test-cases.js 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. /**
  2. * 心知益家小程序 - 自动化测试用例脚本
  3. *
  4. * 测试策略:
  5. * 1. Web端(管理后台):浏览器自动化测试,管理员和指导师登录,测试过程截图
  6. * 2. 小程序端:API接口测试,构造请求数据,验证响应,不进行UI点击测试
  7. *
  8. * 最后更新时间:2026-04-14
  9. */
  10. const TEST_CONFIG = {
  11. // 测试环境配置
  12. baseUrl: 'http://192.168.16.18:8080', // 后端API地址
  13. webAdminUrl: 'http://192.168.16.18:8081', // Web管理端地址
  14. // 测试账号
  15. adminAccount: { phone: '13800138000', password: 'admin123' },
  16. teacherAccount: { phone: '13800138003', password: 'teacher123' },
  17. // API测试账号(小程序端)
  18. apiTestAccounts: {
  19. parent: { id: 1, phone: '13800138001', token: '' },
  20. child: { id: 2, phone: '13800138002', token: '' }
  21. },
  22. // 测试超时时间(毫秒)
  23. timeout: 30000,
  24. // 截图保存路径
  25. screenshotPath: './screenshots'
  26. };
  27. // ============================================================================
  28. // 模块1: API接口测试基类(小程序端)
  29. // ============================================================================
  30. /**
  31. * API测试基类 - 用于小程序端接口测试
  32. * 只测试API接口,记录构造的请求数据和响应结果
  33. */
  34. class ApiTestCase {
  35. constructor(id, description, priority, endpoint, method = 'GET') {
  36. this.id = id;
  37. this.description = description;
  38. this.priority = priority; // P0, P1, P2
  39. this.endpoint = endpoint;
  40. this.method = method;
  41. this.status = 'pending'; // pending, running, passed, failed
  42. this.error = null;
  43. this.requestData = null;
  44. this.responseData = null;
  45. this.screenshots = [];
  46. }
  47. /**
  48. * 发送API请求
  49. */
  50. async sendRequest(data = {}, headers = {}) {
  51. const url = `${TEST_CONFIG.baseUrl}${this.endpoint}`;
  52. const startTime = Date.now();
  53. this.requestData = {
  54. url: url,
  55. method: this.method,
  56. headers: {
  57. 'Content-Type': 'application/json',
  58. ...headers
  59. },
  60. body: data,
  61. timestamp: new Date().toISOString()
  62. };
  63. console.log(`[${this.id}] 发送请求: ${this.method} ${url}`);
  64. console.log(`[${this.id}] 请求数据:`, JSON.stringify(data, null, 2));
  65. try {
  66. // 这里需要实际的HTTP客户端,模拟请求
  67. // 在实际运行时,可以使用 fetch 或 axios
  68. const response = await this.mockFetch(url, {
  69. method: this.method,
  70. headers: this.requestData.headers,
  71. body: this.method !== 'GET' ? JSON.stringify(data) : undefined
  72. });
  73. this.responseData = {
  74. status: response.status,
  75. statusText: response.statusText,
  76. data: response.data,
  77. duration: Date.now() - startTime,
  78. timestamp: new Date().toISOString()
  79. };
  80. console.log(`[${this.id}] 响应状态: ${response.status}`);
  81. console.log(`[${this.id}] 响应数据:`, JSON.stringify(response.data, null, 2));
  82. return response;
  83. } catch (error) {
  84. this.responseData = {
  85. error: error.message,
  86. timestamp: new Date().toISOString()
  87. };
  88. throw error;
  89. }
  90. }
  91. /**
  92. * 模拟fetch请求(实际运行时替换为真实HTTP客户端)
  93. */
  94. async mockFetch(url, options) {
  95. // 模拟响应,实际测试时需要连接真实后端
  96. return {
  97. status: 200,
  98. statusText: 'OK',
  99. data: { code: 200, message: 'success', data: {} }
  100. };
  101. }
  102. /**
  103. * 记录测试结果
  104. */
  105. logResult(passed, error = null) {
  106. this.status = passed ? 'passed' : 'failed';
  107. this.error = error;
  108. console.log(`[${this.id}] 测试${passed ? '通过 ✅' : '失败 ❌'}: ${this.description}`);
  109. if (error) {
  110. console.error(`[${this.id}] 错误信息:`, error);
  111. }
  112. }
  113. /**
  114. * 执行测试(子类实现)
  115. */
  116. async run() {
  117. throw new Error('子类必须实现 run 方法');
  118. }
  119. }
  120. // ============================================================================
  121. // 模块2: Web浏览器测试基类(管理后台)
  122. // ============================================================================
  123. /**
  124. * Web浏览器测试基类 - 用于管理后台UI测试
  125. * 使用Chrome DevTools进行浏览器自动化,测试过程截图
  126. */
  127. class WebTestCase {
  128. constructor(id, description, priority) {
  129. this.id = id;
  130. this.description = description;
  131. this.priority = priority;
  132. this.status = 'pending';
  133. this.error = null;
  134. this.screenshots = [];
  135. this.pageId = null;
  136. }
  137. /**
  138. * 初始化测试环境 - 打开浏览器
  139. */
  140. async setup() {
  141. console.log(`[${this.id}] 测试启动: ${this.description}`);
  142. this.status = 'running';
  143. }
  144. /**
  145. * 标准化快照数据
  146. */
  147. normalizeSnapshot(snapshot) {
  148. if (!snapshot) return [];
  149. if (Array.isArray(snapshot)) return snapshot;
  150. if (Array.isArray(snapshot.elements)) return snapshot.elements;
  151. if (Array.isArray(snapshot.nodes)) return snapshot.nodes;
  152. if (Array.isArray(snapshot.items)) return snapshot.items;
  153. return [];
  154. }
  155. /**
  156. * 通过文本查找元素
  157. */
  158. findByText(snapshot, text) {
  159. const items = this.normalizeSnapshot(snapshot);
  160. return items.find(item => typeof item.text === 'string' && item.text.includes(text));
  161. }
  162. /**
  163. * 截图
  164. */
  165. async screenshot(name) {
  166. const filename = `${TEST_CONFIG.screenshotPath}/${this.id}_${name}.png`;
  167. // 实际运行时调用 chrome-devtools_take_screenshot
  168. console.log(`[${this.id}] 截图保存: ${filename}`);
  169. this.screenshots.push(filename);
  170. return filename;
  171. }
  172. /**
  173. * 等待元素出现
  174. */
  175. async waitForElement(text, timeout = TEST_CONFIG.timeout) {
  176. // 实际运行时调用 chrome-devtools_wait_for
  177. console.log(`[${this.id}] 等待元素: ${text}`);
  178. return { uid: `mock-${text}` };
  179. }
  180. /**
  181. * 点击元素
  182. */
  183. async click(uid) {
  184. // 实际运行时调用 chrome-devtools_click
  185. console.log(`[${this.id}] 点击元素: ${uid}`);
  186. }
  187. /**
  188. * 填充输入框
  189. */
  190. async fill(uid, value) {
  191. // 实际运行时调用 chrome-devtools_fill
  192. console.log(`[${this.id}] 填充输入: ${uid} = ${value}`);
  193. }
  194. /**
  195. * 获取页面快照
  196. */
  197. async getSnapshot() {
  198. // 实际运行时调用 chrome-devtools_take_snapshot
  199. return [];
  200. }
  201. /**
  202. * 登录管理员账号
  203. */
  204. async loginAdmin() {
  205. console.log(`[${this.id}] 登录管理员账号: ${TEST_CONFIG.adminAccount.phone}`);
  206. await this.screenshot('01_login_page');
  207. // 填写登录表单
  208. const phoneInput = await this.waitForElement('手机号');
  209. await this.fill(phoneInput.uid, TEST_CONFIG.adminAccount.phone);
  210. const pwdInput = await this.waitForElement('密码');
  211. await this.fill(pwdInput.uid, TEST_CONFIG.adminAccount.password);
  212. const loginBtn = await this.waitForElement('登录');
  213. await this.click(loginBtn.uid);
  214. await this.screenshot('02_after_login');
  215. }
  216. /**
  217. * 登录指导师账号
  218. */
  219. async loginTeacher() {
  220. console.log(`[${this.id}] 登录指导师账号: ${TEST_CONFIG.teacherAccount.phone}`);
  221. await this.screenshot('01_teacher_login_page');
  222. const phoneInput = await this.waitForElement('手机号');
  223. await this.fill(phoneInput.uid, TEST_CONFIG.teacherAccount.phone);
  224. const pwdInput = await this.waitForElement('密码');
  225. await this.fill(pwdInput.uid, TEST_CONFIG.teacherAccount.password);
  226. const loginBtn = await this.waitForElement('登录');
  227. await this.click(loginBtn.uid);
  228. await this.screenshot('02_teacher_after_login');
  229. }
  230. /**
  231. * 记录测试结果
  232. */
  233. logResult(passed, error = null) {
  234. this.status = passed ? 'passed' : 'failed';
  235. this.error = error;
  236. console.log(`[${this.id}] 测试${passed ? '通过 ✅' : '失败 ❌'}: ${this.description}`);
  237. if (error) {
  238. console.error(`[${this.id}] 错误信息:`, error);
  239. }
  240. }
  241. /**
  242. * 执行测试(子类实现)
  243. */
  244. async run() {
  245. throw new Error('子类必须实现 run 方法');
  246. }
  247. }
  248. // ============================================================================
  249. // 小程序端 API 测试用例
  250. // ============================================================================
  251. /**
  252. * API-001: 家长登录接口
  253. */
  254. class API_001_TestCase extends ApiTestCase {
  255. constructor() {
  256. super('API-001', '家长登录接口', 'P0', '/api/auth/login', 'POST');
  257. }
  258. async run() {
  259. try {
  260. this.requestData = {
  261. phone: '13800138001',
  262. code: '123456',
  263. role: 'parent'
  264. };
  265. const response = await this.sendRequest(this.requestData);
  266. // 验证响应
  267. const passed = response.status === 200 && response.data.code === 200;
  268. this.logResult(passed);
  269. } catch (error) {
  270. this.logResult(false, error);
  271. }
  272. }
  273. }
  274. /**
  275. * API-002: 创建任务接口
  276. */
  277. class API_002_TestCase extends ApiTestCase {
  278. constructor() {
  279. super('API-002', '家长创建任务接口', 'P0', '/api/tasks', 'POST');
  280. }
  281. async run() {
  282. try {
  283. this.requestData = {
  284. childId: 2,
  285. name: '完成语文作业',
  286. points: 5,
  287. category: 'study',
  288. deadline: '2026-04-15T18:00:00',
  289. description: '完成课本第10页到第15页的练习题'
  290. };
  291. const response = await this.sendRequest(this.requestData);
  292. const passed = response.status === 200;
  293. this.logResult(passed);
  294. } catch (error) {
  295. this.logResult(false, error);
  296. }
  297. }
  298. }
  299. /**
  300. * API-003: 获取今日任务列表
  301. */
  302. class API_003_TestCase extends ApiTestCase {
  303. constructor() {
  304. super('API-003', '获取孩子今日任务列表', 'P0', '/api/tasks/today/2', 'GET');
  305. }
  306. async run() {
  307. try {
  308. this.requestData = {
  309. childId: 2,
  310. date: new Date().toISOString().split('T')[0]
  311. };
  312. const response = await this.sendRequest(this.requestData);
  313. // 记录响应数据结构
  314. console.log(`[${this.id}] 任务列表数据结构:`, JSON.stringify(response.data, null, 2));
  315. const passed = response.status === 200;
  316. this.logResult(passed);
  317. } catch (error) {
  318. this.logResult(false, error);
  319. }
  320. }
  321. }
  322. /**
  323. * API-004: 孩子完成任务
  324. */
  325. class API_004_TestCase extends ApiTestCase {
  326. constructor() {
  327. super('API-004', '孩子完成任务接口', 'P0', '/api/tasks/1/complete', 'POST');
  328. }
  329. async run() {
  330. try {
  331. this.requestData = {
  332. taskId: 1,
  333. completedAt: new Date().toISOString(),
  334. proof: {
  335. type: 'photo',
  336. url: 'https://example.com/photo.jpg',
  337. description: '已完成作业拍照'
  338. }
  339. };
  340. const response = await this.sendRequest(this.requestData);
  341. const passed = response.status === 200;
  342. this.logResult(passed);
  343. } catch (error) {
  344. this.logResult(false, error);
  345. }
  346. }
  347. }
  348. /**
  349. * API-005: 获取积分记录
  350. */
  351. class API_005_TestCase extends ApiTestCase {
  352. constructor() {
  353. super('API-005', '获取积分记录接口', 'P0', '/api/points/records', 'GET');
  354. }
  355. async run() {
  356. try {
  357. this.requestData = {
  358. childId: 2,
  359. startDate: '2026-04-01',
  360. endDate: '2026-04-30'
  361. };
  362. const response = await this.sendRequest(this.requestData);
  363. console.log(`[${this.id}] 积分记录数据:`, JSON.stringify(response.data, null, 2));
  364. const passed = response.status === 200;
  365. this.logResult(passed);
  366. } catch (error) {
  367. this.logResult(false, error);
  368. }
  369. }
  370. }
  371. /**
  372. * API-006: 添加心愿到心愿单
  373. */
  374. class API_006_TestCase extends ApiTestCase {
  375. constructor() {
  376. super('API-006', '孩子添加心愿接口', 'P0', '/api/rewards/wishlist', 'POST');
  377. }
  378. async run() {
  379. try {
  380. this.requestData = {
  381. childId: 2,
  382. name: '去游乐园玩',
  383. category: 'experience',
  384. description: '周末全家一起去游乐园'
  385. };
  386. const response = await this.sendRequest(this.requestData);
  387. const passed = response.status === 200;
  388. this.logResult(passed);
  389. } catch (error) {
  390. this.logResult(false, error);
  391. }
  392. }
  393. }
  394. /**
  395. * API-007: 兑换奖励
  396. */
  397. class API_007_TestCase extends ApiTestCase {
  398. constructor() {
  399. super('API-007', '孩子兑换奖励接口', 'P0', '/api/rewards/1/exchange', 'POST');
  400. }
  401. async run() {
  402. try {
  403. this.requestData = {
  404. rewardId: 1,
  405. childId: 2,
  406. pointsUsed: 50
  407. };
  408. const response = await this.sendRequest(this.requestData);
  409. const passed = response.status === 200;
  410. this.logResult(passed);
  411. } catch (error) {
  412. this.logResult(false, error);
  413. }
  414. }
  415. }
  416. /**
  417. * API-008: 家长审批奖励兑换
  418. */
  419. class API_008_TestCase extends ApiTestCase {
  420. constructor() {
  421. super('API-008', '家长审批奖励兑换接口', 'P0', '/api/rewards/1/approve', 'POST');
  422. }
  423. async run() {
  424. try {
  425. this.requestData = {
  426. exchangeId: 1,
  427. approved: true,
  428. comment: '同意兑换,表现很好!'
  429. };
  430. const response = await this.sendRequest(this.requestData);
  431. const passed = response.status === 200;
  432. this.logResult(passed);
  433. } catch (error) {
  434. this.logResult(false, error);
  435. }
  436. }
  437. }
  438. /**
  439. * API-009: 获取连续打卡天数
  440. */
  441. class API_009_TestCase extends ApiTestCase {
  442. constructor() {
  443. super('API-009', '获取连续打卡天数接口', 'P0', '/api/streak/2', 'GET');
  444. }
  445. async run() {
  446. try {
  447. this.requestData = {
  448. childId: 2
  449. };
  450. const response = await this.sendRequest(this.requestData);
  451. console.log(`[${this.id}] 连续打卡数据:`, JSON.stringify(response.data, null, 2));
  452. const passed = response.status === 200;
  453. this.logResult(passed);
  454. } catch (error) {
  455. this.logResult(false, error);
  456. }
  457. }
  458. }
  459. /**
  460. * API-010: 获取家庭信息
  461. */
  462. class API_010_TestCase extends ApiTestCase {
  463. constructor() {
  464. super('API-010', '获取家庭信息接口', 'P0', '/api/family/info', 'GET');
  465. }
  466. async run() {
  467. try {
  468. this.requestData = {
  469. familyId: 1
  470. };
  471. const response = await this.sendRequest(this.requestData);
  472. console.log(`[${this.id}] 家庭信息数据:`, JSON.stringify(response.data, null, 2));
  473. const passed = response.status === 200;
  474. this.logResult(passed);
  475. } catch (error) {
  476. this.logResult(false, error);
  477. }
  478. }
  479. }
  480. // ============================================================================
  481. // Web管理端 浏览器测试用例
  482. // ============================================================================
  483. /**
  484. * WEB-001: 管理员登录测试
  485. */
  486. class WEB_001_TestCase extends WebTestCase {
  487. constructor() {
  488. super('WEB-001', '管理员登录Web后台', 'P0');
  489. }
  490. async run() {
  491. try {
  492. await this.setup();
  493. // 1. 打开登录页面
  494. console.log(`[${this.id}] 访问: ${TEST_CONFIG.webAdminUrl}`);
  495. await this.screenshot('01_login_page');
  496. // 2. 输入账号密码
  497. const phoneInput = await this.waitForElement('手机号');
  498. await this.fill(phoneInput.uid, TEST_CONFIG.adminAccount.phone);
  499. const pwdInput = await this.waitForElement('密码');
  500. await this.fill(pwdInput.uid, TEST_CONFIG.adminAccount.password);
  501. await this.screenshot('02_form_filled');
  502. // 3. 点击登录
  503. const loginBtn = await this.waitForElement('登录');
  504. await this.click(loginBtn.uid);
  505. await this.screenshot('03_after_login');
  506. // 4. 验证登录成功
  507. const result = await this.getSnapshot();
  508. const hasDashboard = result.some(item =>
  509. item.text.includes('仪表板') ||
  510. item.text.includes('管理') ||
  511. item.text.includes('统计')
  512. );
  513. this.logResult(hasDashboard);
  514. } catch (error) {
  515. this.logResult(false, error);
  516. }
  517. }
  518. }
  519. /**
  520. * WEB-002: 管理员查看用户列表
  521. */
  522. class WEB_002_TestCase extends WebTestCase {
  523. constructor() {
  524. super('WEB-002', '管理员查看用户列表', 'P0');
  525. }
  526. async run() {
  527. try {
  528. await this.setup();
  529. await this.loginAdmin();
  530. // 1. 点击用户管理
  531. const userMenu = await this.waitForElement('用户管理');
  532. await this.click(userMenu.uid);
  533. await this.screenshot('01_user_menu');
  534. // 2. 查看用户列表
  535. const userList = await this.waitForElement('用户列表');
  536. await this.click(userList.uid);
  537. await this.screenshot('02_user_list');
  538. // 3. 验证列表显示
  539. const result = await this.getSnapshot();
  540. const hasUsers = result.some(item =>
  541. item.text.includes('家长') ||
  542. item.text.includes('孩子') ||
  543. item.text.includes('手机号')
  544. );
  545. this.logResult(hasUsers);
  546. } catch (error) {
  547. this.logResult(false, error);
  548. }
  549. }
  550. }
  551. /**
  552. * WEB-003: 管理员查看任务统计
  553. */
  554. class WEB_003_TestCase extends WebTestCase {
  555. constructor() {
  556. super('WEB-003', '管理员查看任务统计', 'P0');
  557. }
  558. async run() {
  559. try {
  560. await this.setup();
  561. await this.loginAdmin();
  562. // 1. 进入任务管理
  563. const taskMenu = await this.waitForElement('任务管理');
  564. await this.click(taskMenu.uid);
  565. await this.screenshot('01_task_menu');
  566. // 2. 查看统计
  567. const stats = await this.waitForElement('统计');
  568. await this.click(stats.uid);
  569. await this.screenshot('02_task_stats');
  570. // 3. 验证统计图表
  571. const result = await this.getSnapshot();
  572. const hasStats = result.some(item =>
  573. item.text.includes('完成率') ||
  574. item.text.includes('任务数') ||
  575. item.text.includes('统计')
  576. );
  577. this.logResult(hasStats);
  578. } catch (error) {
  579. this.logResult(false, error);
  580. }
  581. }
  582. }
  583. /**
  584. * WEB-004: 指导师登录测试
  585. */
  586. class WEB_004_TestCase extends WebTestCase {
  587. constructor() {
  588. super('WEB-004', '指导师登录Web后台', 'P0');
  589. }
  590. async run() {
  591. try {
  592. await this.setup();
  593. // 1. 打开登录页面
  594. console.log(`[${this.id}] 访问: ${TEST_CONFIG.webAdminUrl}`);
  595. await this.screenshot('01_teacher_login_page');
  596. // 2. 输入指导师账号
  597. const phoneInput = await this.waitForElement('手机号');
  598. await this.fill(phoneInput.uid, TEST_CONFIG.teacherAccount.phone);
  599. const pwdInput = await this.waitForElement('密码');
  600. await this.fill(pwdInput.uid, TEST_CONFIG.teacherAccount.password);
  601. await this.screenshot('02_teacher_form_filled');
  602. // 3. 登录
  603. const loginBtn = await this.waitForElement('登录');
  604. await this.click(loginBtn.uid);
  605. await this.screenshot('03_teacher_after_login');
  606. // 4. 验证指导师界面
  607. const result = await this.getSnapshot();
  608. const hasGuideFeatures = result.some(item =>
  609. item.text.includes('指导') ||
  610. item.text.includes('家庭') ||
  611. item.text.includes('任务')
  612. );
  613. this.logResult(hasGuideFeatures);
  614. } catch (error) {
  615. this.logResult(false, error);
  616. }
  617. }
  618. }
  619. /**
  620. * WEB-005: 指导师查看分配的家庭
  621. */
  622. class WEB_005_TestCase extends WebTestCase {
  623. constructor() {
  624. super('WEB-005', '指导师查看分配的家庭', 'P0');
  625. }
  626. async run() {
  627. try {
  628. await this.setup();
  629. await this.loginTeacher();
  630. // 1. 查看我的家庭
  631. const myFamilies = await this.waitForElement('我的家庭');
  632. await this.click(myFamilies.uid);
  633. await this.screenshot('01_my_families');
  634. // 2. 验证家庭列表
  635. const result = await this.getSnapshot();
  636. const hasFamilies = result.some(item =>
  637. item.text.includes('家庭') ||
  638. item.text.includes('孩子')
  639. );
  640. this.logResult(hasFamilies);
  641. } catch (error) {
  642. this.logResult(false, error);
  643. }
  644. }
  645. }
  646. /**
  647. * WEB-006: 管理员审核指导师注册
  648. */
  649. class WEB_006_TestCase extends WebTestCase {
  650. constructor() {
  651. super('WEB-006', '管理员审核指导师注册', 'P1');
  652. }
  653. async run() {
  654. try {
  655. await this.setup();
  656. await this.loginAdmin();
  657. // 1. 进入指导师管理
  658. const guideMenu = await this.waitForElement('指导师管理');
  659. await this.click(guideMenu.uid);
  660. await this.screenshot('01_guide_menu');
  661. // 2. 查看待审核
  662. const pendingBtn = await this.waitForElement('待审核');
  663. await this.click(pendingBtn.uid);
  664. await this.screenshot('02_pending_guides');
  665. // 3. 审核操作
  666. const approveBtn = await this.waitForElement('通过');
  667. await this.click(approveBtn.uid);
  668. await this.screenshot('03_approved');
  669. this.logResult(true);
  670. } catch (error) {
  671. this.logResult(false, error);
  672. }
  673. }
  674. }
  675. /**
  676. * WEB-007: 管理员查看街道统计
  677. */
  678. class WEB_007_TestCase extends WebTestCase {
  679. constructor() {
  680. super('WEB-007', '管理员查看街道统计', 'P1');
  681. }
  682. async run() {
  683. try {
  684. await this.setup();
  685. await this.loginAdmin();
  686. // 1. 进入统计分析
  687. const statsMenu = await this.waitForElement('统计分析');
  688. await this.click(statsMenu.uid);
  689. await this.screenshot('01_stats_menu');
  690. // 2. 街道分布
  691. const streetBtn = await this.waitForElement('街道分布');
  692. await this.click(streetBtn.uid);
  693. await this.screenshot('02_street_stats');
  694. this.logResult(true);
  695. } catch (error) {
  696. this.logResult(false, error);
  697. }
  698. }
  699. }
  700. // ============================================================================
  701. // 测试套件管理
  702. // ============================================================================
  703. class TestSuite {
  704. constructor() {
  705. this.testCases = [];
  706. this.results = {
  707. total: 0,
  708. passed: 0,
  709. failed: 0,
  710. skipped: 0,
  711. coverage: {
  712. P0: { total: 0, passed: 0 },
  713. P1: { total: 0, passed: 0 },
  714. P2: { total: 0, passed: 0 }
  715. }
  716. };
  717. this.apiTestData = []; // 记录所有API测试数据
  718. this.webTestScreenshots = []; // 记录所有Web测试截图
  719. }
  720. /**
  721. * 注册测试用例
  722. */
  723. register(testCase) {
  724. this.testCases.push(testCase);
  725. this.results.total++;
  726. this.results.coverage[testCase.priority].total++;
  727. }
  728. /**
  729. * 运行所有测试
  730. */
  731. async runAll() {
  732. console.log('========================================');
  733. console.log('心知益家 - 自动化测试开始');
  734. console.log(`测试用例总数: ${this.testCases.length}`);
  735. console.log('========================================\n');
  736. for (const testCase of this.testCases) {
  737. console.log(`\n>>> 执行测试: ${testCase.id} - ${testCase.description}`);
  738. try {
  739. await testCase.run();
  740. if (testCase.status === 'passed') {
  741. this.results.passed++;
  742. this.results.coverage[testCase.priority].passed++;
  743. } else {
  744. this.results.failed++;
  745. }
  746. // 收集测试数据
  747. if (testCase instanceof ApiTestCase) {
  748. this.apiTestData.push({
  749. id: testCase.id,
  750. description: testCase.description,
  751. request: testCase.requestData,
  752. response: testCase.responseData,
  753. status: testCase.status
  754. });
  755. } else if (testCase instanceof WebTestCase) {
  756. this.webTestScreenshots.push({
  757. id: testCase.id,
  758. description: testCase.description,
  759. screenshots: testCase.screenshots,
  760. status: testCase.status
  761. });
  762. }
  763. } catch (error) {
  764. console.error(`[${testCase.id}] 测试执行异常:`, error);
  765. this.results.failed++;
  766. testCase.status = 'failed';
  767. testCase.error = error;
  768. }
  769. }
  770. this.generateReport();
  771. }
  772. /**
  773. * 按优先级运行测试
  774. */
  775. async runByPriority(priority) {
  776. const filteredTests = this.testCases.filter(tc => tc.priority === priority);
  777. console.log(`\n运行 ${priority} 级测试用例: ${filteredTests.length} 个\n`);
  778. for (const testCase of filteredTests) {
  779. await testCase.run();
  780. if (testCase.status === 'passed') {
  781. this.results.passed++;
  782. this.results.coverage[testCase.priority].passed++;
  783. } else {
  784. this.results.failed++;
  785. }
  786. }
  787. this.generateReport();
  788. }
  789. /**
  790. * 生成测试报告
  791. */
  792. generateReport() {
  793. console.log('\n========================================');
  794. console.log('测试执行完成 - 测试报告');
  795. console.log('========================================');
  796. console.log(`总测试数: ${this.results.total}`);
  797. console.log(`通过: ${this.results.passed} ✅`);
  798. console.log(`失败: ${this.results.failed} ❌`);
  799. console.log(`跳过: ${this.results.skipped} ⏭️`);
  800. console.log(`通过率: ${((this.results.passed / this.results.total) * 100).toFixed(2)}%`);
  801. console.log('\n需求覆盖度:');
  802. console.log(` P0: ${this.results.coverage.P0.passed}/${this.results.coverage.P0.total}`);
  803. console.log(` P1: ${this.results.coverage.P1.passed}/${this.results.coverage.P1.total}`);
  804. console.log(` P2: ${this.results.coverage.P2.passed}/${this.results.coverage.P2.total}`);
  805. console.log('========================================\n');
  806. // 输出API测试数据汇总
  807. if (this.apiTestData.length > 0) {
  808. console.log('\n========================================');
  809. console.log('API接口测试数据汇总');
  810. console.log('========================================');
  811. this.apiTestData.forEach(test => {
  812. console.log(`\n[${test.id}] ${test.description}`);
  813. console.log('请求:', JSON.stringify(test.request, null, 2));
  814. console.log('响应:', JSON.stringify(test.response, null, 2));
  815. });
  816. }
  817. // 输出Web测试截图汇总
  818. if (this.webTestScreenshots.length > 0) {
  819. console.log('\n========================================');
  820. console.log('Web浏览器测试截图汇总');
  821. console.log('========================================');
  822. this.webTestScreenshots.forEach(test => {
  823. console.log(`\n[${test.id}] ${test.description}`);
  824. console.log('截图:', test.screenshots.join(', '));
  825. });
  826. }
  827. // 生成JSON报告
  828. const report = {
  829. timestamp: new Date().toISOString(),
  830. summary: this.results,
  831. apiTests: this.apiTestData,
  832. webTests: this.webTestScreenshots,
  833. details: this.testCases.map(tc => ({
  834. id: tc.id,
  835. description: tc.description,
  836. priority: tc.priority,
  837. status: tc.status,
  838. error: tc.error?.message || null,
  839. screenshots: tc.screenshots || [],
  840. requestData: tc.requestData || null,
  841. responseData: tc.responseData || null
  842. }))
  843. };
  844. return report;
  845. }
  846. }
  847. // ============================================================================
  848. // 导出测试用例和运行器
  849. // ============================================================================
  850. module.exports = {
  851. TestSuite,
  852. TEST_CONFIG,
  853. // API测试用例(小程序端)
  854. apiTestCases: {
  855. API_001_TestCase,
  856. API_002_TestCase,
  857. API_003_TestCase,
  858. API_004_TestCase,
  859. API_005_TestCase,
  860. API_006_TestCase,
  861. API_007_TestCase,
  862. API_008_TestCase,
  863. API_009_TestCase,
  864. API_010_TestCase
  865. },
  866. // Web浏览器测试用例(管理后台)
  867. webTestCases: {
  868. WEB_001_TestCase,
  869. WEB_002_TestCase,
  870. WEB_003_TestCase,
  871. WEB_004_TestCase,
  872. WEB_005_TestCase,
  873. WEB_006_TestCase,
  874. WEB_007_TestCase
  875. }
  876. };
  877. // ============================================================================
  878. // 主程序入口
  879. // ============================================================================
  880. async function main() {
  881. const suite = new TestSuite();
  882. // 注册API测试用例(小程序端)
  883. Object.values(module.exports.apiTestCases).forEach(TestCase => {
  884. const testCase = new TestCase();
  885. if (testCase.priority === 'P0') {
  886. suite.register(testCase);
  887. }
  888. });
  889. // 注册Web测试用例(管理后台)
  890. Object.values(module.exports.webTestCases).forEach(TestCase => {
  891. const testCase = new TestCase();
  892. if (testCase.priority === 'P0') {
  893. suite.register(testCase);
  894. }
  895. });
  896. // 运行测试
  897. await suite.runAll();
  898. }
  899. // 如果直接运行此脚本
  900. if (require.main === module) {
  901. main().catch(console.error);
  902. }