/** * ================================================================ * 场景:心愿兑换审批全流程 E2E 测试 * ================================================================ * 用户故事:作为孩子,我希望创建心愿并通过积累积分兑换, * 以获得家长的认可和实际的奖励物品。 * * 流程步骤: * 1. 孩子创建心愿(如"想要一套科学实验套装") * 2. 家长查看心愿并设定所需积分(如 500 积分) * 3. 孩子通过完成任务积累积分后,申请兑换心愿 * 4. 家长审批通过,系统自动扣除积分 * 5. 家长购买物品后标记"已购买" * 6. 孩子确认收货,心愿流程完成 * * 关键里程碑: * - [Milestone-1] 心愿创建成功,获得心愿ID * - [Milestone-2] 家长定价成功,心愿状态变更为"已定价" * - [Milestone-3] 兑换申请提交,状态变更为"兑换中" * - [Milestone-4] 家长审批通过,积分立即扣减 * - [Milestone-5] 标记已购买,等待孩子确认 * - [Milestone-6] 孩子确认收货,心愿完成 * ================================================================ * * 运行:npx playwright test tests/e2e/wish-exchange-flow.spec.js */ const { test, expect } = require('@playwright/test'); test.describe('【场景流程】心愿兑换审批全流程', () => { // ===== 场景1:孩子创建心愿 ===== /** * 场景:孩子创建一个新的心愿 * 角色:孩子 * 触发条件:孩子点击"创建心愿"并填写表单 */ test('[场景1] 孩子创建心愿 - 期望心愿创建成功', async ({ page }) => { // ========== Given:进入创建心愿页面 ========== await page.goto('/#/pages/wishes/create'); await page.waitForLoadState('networkidle'); // ========== When:填写心愿信息 ========== // 输入心愿标题 const titleInput = page.locator('input[placeholder*="心愿"], .wish-title-input, input[name="title"]'); if (await titleInput.isVisible()) { await titleInput.fill('科学实验套装'); } // 输入心愿描述 const descInput = page.locator('textarea[placeholder*="描述"], .wish-desc-input'); if (await descInput.isVisible()) { await descInput.fill('包含50个实验的科学实验套装,寓教于乐'); } // 输入预估价格 const priceInput = page.locator('input[placeholder*="价格"], .wish-price-input, input[name="estimatedPrice"]'); if (await priceInput.isVisible()) { await priceInput.fill('299'); } // ========== Then:提交创建 ========== await page.click('.submit-btn, .create-btn'); // ========== Then:Milestone-1 - 心愿创建成功 ======== await page.waitForSelector('.success-tip, .wish-created, .create-success', { timeout: 10000 }); // 验证心愿创建成功 const successTip = page.locator('.success-tip, .wish-created, .create-success'); expect(await successTip.isVisible()).toBeTruthy(); }); // ===== 场景2:家长定价 ===== /** * 场景:家长为孩子的心愿设定所需积分 * 角色:家长 * 触发条件:家长打开心愿详情,决定设定多少积分可兑换 */ test('[场景2] 家长设定心愿积分 - 期望积分设定成功且状态更新', async ({ page }) => { // ========== Given:进入待定价心愿列表 ========== await page.goto('/#/pages/wishes/pending'); await page.waitForLoadState('networkidle'); // ========== Step 1:找到待定价心愿 ========== await page.waitForSelector('.wish-item', { timeout: 10000 }); const pendingWish = page.locator('.wish-item:has-text("待定价"), .wish-item:has-text("待设置")').first(); await pendingWish.click(); // ========== Step 2:设定积分值 ========== await page.waitForSelector('.price-input, .points-input', { timeout: 5000 }); const priceInput = page.locator('.price-input, .points-input, input[name="pointsRequired"]'); await priceInput.fill('500'); // ========== When:提交定价 ========== await page.click('.set-price-btn, .confirm-price-btn, .submit-btn'); // ========== Then:Milestone-2 - 定价成功 ======== await page.waitForSelector('.success-tip, .price-set, .set-success', { timeout: 10000 }); // 验证状态变更 const wishStatus = page.locator('.wish-status, .status-tag'); if (await wishStatus.isVisible()) { const statusText = await wishStatus.textContent(); expect(statusText).toMatch(/已定价|待兑换/); } }); /** * 场景:家长设定的积分值为负数或零 * 角色:家长 * 触发条件:表单校验未通过 */ test('[场景2b] 家长设定无效积分值 - 期望返回错误提示', async ({ page }) => { // ========== Given:进入心愿编辑页面 ========== await page.goto('/#/pages/wishes/edit/1'); await page.waitForLoadState('networkidle'); // ========== When:设定无效积分值 ========== const priceInput = page.locator('.price-input, .points-input, input[name="pointsRequired"]'); if (await priceInput.isVisible()) { await priceInput.fill('0'); } await page.click('.submit-btn'); // ========== Then:返回错误提示 ======== await page.waitForSelector('.error-tip, .van-field__error, .form-error', { timeout: 5000 }); const errorTip = page.locator('.error-tip, .van-field__error, .form-error'); expect(await errorTip.isVisible()).toBeTruthy(); }); // ===== 场景3:孩子申请兑换 ===== /** * 场景:孩子积累足够积分后,申请兑换心愿 * 角色:孩子 * 触发条件:孩子点击心愿的"申请兑换"按钮 */ test('[场景3] 孩子申请兑换心愿 - 期望兑换申请提交成功', async ({ page }) => { // ========== Given:进入我的心愿列表 ========== await page.goto('/#/pages/wishes/my'); await page.waitForLoadState('networkidle'); // ========== Step 1:找到已定价心愿 ========== await page.waitForSelector('.wish-item', { timeout: 10000 }); const pricedWish = page.locator('.wish-item:has-text("已定价"), .wish-item:has-text("可兑换")').first(); await pricedWish.click(); // ========== Step 2:点击兑换按钮 ========== await page.waitForSelector('.exchange-btn, .apply-exchange-btn', { timeout: 5000 }); await page.click('.exchange-btn, .apply-exchange-btn'); // ========== Then:Milestone-3 - 兑换申请提交成功 ======== await page.waitForSelector('.success-tip, .exchange-applied, .apply-success', { timeout: 10000 }); const successTip = page.locator('.success-tip, .exchange-applied, .apply-success'); expect(await successTip.isVisible()).toBeTruthy(); }); /** * 场景:孩子积分不足时尝试兑换 * 角色:孩子 * 触发条件:孩子积分不足以兑换该心愿 */ test('[场景3b] 积分不足时申请兑换 - 期望返回错误提示', async ({ page }) => { // ========== Given:进入心愿详情 ========== await page.goto('/#/pages/wishes/detail/1'); await page.waitForLoadState('networkidle'); // ========== When:点击兑换 ========== const exchangeBtn = page.locator('.exchange-btn, .apply-exchange-btn'); if (await exchangeBtn.isVisible()) { await exchangeBtn.click(); // ========== Then:返回积分不足提示 ======== await page.waitForSelector('.error-tip, .insufficient-points, .points-error', { timeout: 5000 }); const errorTip = page.locator('.error-tip, .insufficient-points, .points-error'); expect(await errorTip.isVisible()).toBeTruthy(); // 验证错误信息包含积分相关字样 const errorText = await errorTip.textContent(); expect(errorText).toMatch(/积分|不足/); } }); // ===== 场景4:家长审批兑换 ===== /** * 场景:家长审批通过孩子的兑换申请,积分立即扣除 * 角色:家长 * 触发条件:家长查看兑换申请并点击"批准" */ test('[场景4] 家长审批通过兑换 - 期望审批成功且积分扣除', async ({ page }) => { // ========== Given:进入待审批兑换列表 ========== await page.goto('/#/pages/wishes/pending-approval'); await page.waitForLoadState('networkidle'); // ========== Step 1:找到待审批的兑换申请 ========== await page.waitForSelector('.exchange-item, .approval-item', { timeout: 10000 }); const pendingApproval = page.locator('.exchange-item:has-text("申请中"), .approval-item:has-text("待审批")').first(); await pendingApproval.click(); // ========== Step 2:查看积分信息 ========== await page.waitForSelector('.points-required, .wish-points', { timeout: 5000 }); // ========== When:点击批准 ========== await page.click('.approve-btn, .confirm-btn, .approve-exchange-btn'); // ========== Then:Milestone-4 - 审批通过 + 积分扣减 ======== await page.waitForSelector('.success-tip, .approved, .approve-success', { timeout: 10000 }); // 验证积分已扣减(如果有显示) const remainingPoints = page.locator('.remaining-points, .current-points'); if (await remainingPoints.isVisible()) { const pointsText = await remainingPoints.textContent(); expect(pointsText).toMatch(/\d+/); } }); /** * 场景:家长拒绝孩子的兑换申请 * 角色:家长 * 触发条件:家长认为该心愿不合适,点击"拒绝" */ test('[场景4b] 家长拒绝兑换申请 - 期望心愿状态回退', async ({ page }) => { // ========== Given:进入待审批兑换详情 ========== await page.goto('/#/pages/wishes/approval/1'); await page.waitForLoadState('networkidle'); // ========== When:点击拒绝 ========== await page.click('.reject-btn, .reject-exchange-btn'); // 填写拒绝原因 const reasonInput = page.locator('textarea[placeholder*="原因"], .reject-reason-input'); if (await reasonInput.isVisible()) { await reasonInput.fill('本月预算已用完,下个月再兑换好吗?'); } // 确认拒绝 await page.click('.confirm-reject-btn, .submit-btn'); // ========== Then:拒绝成功,心愿状态回退 ======== await page.waitForSelector('.rejected, .reject-success', { timeout: 10000 }); // 验证心愿状态回退到"已定价" const wishStatus = page.locator('.wish-status, .status-tag'); if (await wishStatus.isVisible()) { const statusText = await wishStatus.textContent(); expect(statusText).toMatch(/已定价|待兑换/); } }); // ===== 场景5:标记已购买 ===== /** * 场景:家长购买物品后标记心愿为"已购买" * 角色:家长 * 触发条件:家长完成购买操作 */ test('[场景5] 家长标记心愿已购买 - 期望状态变更为待收货', async ({ page }) => { // ========== Given:进入已批准兑换列表 ========== await page.goto('/#/pages/wishes/approved'); await page.waitForLoadState('networkidle'); // ========== Step 1:找到已批准的兑换 ========== await page.waitForSelector('.exchange-item, .approved-item', { timeout: 10000 }); const approvedItem = page.locator('.exchange-item:has-text("已批准"), .approved-item').first(); await approvedItem.click(); // ========== When:点击"已购买" ========== await page.click('.mark-purchased-btn, .fulfill-btn'); // ========== Then:Milestone-5 - 标记成功 ======== await page.waitForSelector('.success-tip, .purchased, .fulfill-success', { timeout: 10000 }); // 验证状态变更为"待收货" const wishStatus = page.locator('.wish-status, .status-tag'); if (await wishStatus.isVisible()) { const statusText = await wishStatus.textContent(); expect(statusText).toMatch(/待收货|已购买/); } }); // ===== 场景6:孩子确认收货 ===== /** * 场景:孩子收到物品后确认收货,心愿流程完成 * 角色:孩子 * 触发条件:孩子收到实物并确认无误 */ test('[场景6] 孩子确认收货 - 期望心愿流程完成', async ({ page }) => { // ========== Given:进入待确认收货列表 ========== await page.goto('/#/pages/wishes/to-confirm'); await page.waitForLoadState('networkidle'); // ========== Step 1:找到待确认的心愿 ========== await page.waitForSelector('.wish-item', { timeout: 10000 }); const toConfirm = page.locator('.wish-item:has-text("待收货"), .wish-item:has-text("已购买")').first(); await toConfirm.click(); // ========== When:点击"确认收货" ========== await page.click('.confirm-receive-btn, .confirm-btn'); // ========== Then:Milestone-6 - 心愿完成 ======== await page.waitForSelector('.success-tip, .completed, .confirm-success', { timeout: 10000 }); // 验证状态变更为"已完成" const wishStatus = page.locator('.wish-status, .status-tag'); if (await wishStatus.isVisible()) { const statusText = await wishStatus.textContent(); expect(statusText).toMatch(/已完成|已收货/); } }); // ===== 场景7:完整心愿流程(端到端)===== test('[场景7] 完整心愿生命周期 - 期望全流程成功', async ({ page }) => { // ========== Step 1:孩子创建心愿 ========== await page.goto('/#/pages/wishes/create'); await page.waitForLoadState('networkidle'); const titleInput = page.locator('input[name="title"], .wish-title-input'); if (await titleInput.isVisible()) { await titleInput.fill('科学杂志订阅_' + Date.now()); } const descInput = page.locator('textarea[name="description"], .wish-desc-input'); if (await descInput.isVisible()) { await descInput.fill('儿童科学爱好者订阅一年'); } await page.click('.submit-btn'); await page.waitForSelector('.success-tip, .wish-created', { timeout: 10000 }); // ========== Step 2:家长定价 ========== await page.goto('/#/pages/wishes/pending'); await page.waitForLoadState('networkidle'); await page.waitForSelector('.wish-item', { timeout: 10000 }); const wishItem = page.locator('.wish-item').first(); const editBtn = wishItem.locator('.edit-btn, .price-btn'); if (await editBtn.isVisible()) { await editBtn.click(); await page.waitForSelector('.price-input', { timeout: 5000 }); const priceInput = page.locator('.price-input, input[name="pointsRequired"]'); await priceInput.fill('100'); await page.click('.set-price-btn'); await page.waitForSelector('.success-tip', { timeout: 10000 }); } // ========== Step 3:孩子申请兑换 ========== await page.goto('/#/pages/wishes/my'); await page.waitForLoadState('networkidle'); await page.waitForSelector('.wish-item', { timeout: 10000 }); const exchangeBtn = page.locator('.exchange-btn').first(); if (await exchangeBtn.isVisible()) { await exchangeBtn.click(); await page.waitForSelector('.success-tip, .exchange-applied', { timeout: 10000 }); } // ========== Step 4:家长审批通过 ========== await page.goto('/#/pages/wishes/pending-approval'); await page.waitForLoadState('networkidle'); const approvalItem = page.locator('.exchange-item, .approval-item').first(); if (await approvalItem.isVisible()) { await approvalItem.click(); await page.waitForSelector('.approve-btn', { timeout: 5000 }); await page.click('.approve-btn'); await page.waitForSelector('.success-tip, .approved', { timeout: 10000 }); } // ========== Step 5:家长标记已购买 ========== await page.goto('/#/pages/wishes/approved'); await page.waitForLoadState('networkidle'); const purchasedBtn = page.locator('.mark-purchased-btn, .fulfill-btn').first(); if (await purchasedBtn.isVisible()) { await purchasedBtn.click(); await page.waitForSelector('.success-tip, .purchased', { timeout: 10000 }); } // ========== Step 6:孩子确认收货 ========== await page.goto('/#/pages/wishes/to-confirm'); await page.waitForLoadState('networkidle'); const confirmBtn = page.locator('.confirm-receive-btn').first(); if (await confirmBtn.isVisible()) { await confirmBtn.click(); await page.waitForSelector('.success-tip, .completed', { timeout: 10000 }); } // 验证最终状态 const finalStatus = page.locator('.wish-status'); if (await finalStatus.isVisible()) { const statusText = await finalStatus.textContent(); expect(statusText).toMatch(/已完成/); } }); // ===== 场景8:家长拒绝心愿 ===== test('[场景8] 家长直接拒绝心愿 - 期望心愿被关闭', async ({ page }) => { // ========== Given:进入待处理心愿 ========== await page.goto('/#/pages/wishes/pending'); await page.waitForLoadState('networkidle'); // ========== Step 1:选择心愿 ========== await page.waitForSelector('.wish-item', { timeout: 10000 }); const wishItem = page.locator('.wish-item').first(); await wishItem.click(); // ========== When:点击拒绝 ========== await page.waitForSelector('.reject-btn, .refuse-btn', { timeout: 5000 }); await page.click('.reject-btn'); // 填写原因 const reasonInput = page.locator('textarea[name="reason"], .reject-reason-input'); if (await reasonInput.isVisible()) { await reasonInput.fill('这套书家里已经有了,换个其他的吧'); } await page.click('.confirm-reject-btn'); // ========== Then:心愿被关闭 ======== await page.waitForSelector('.rejected, .reject-success', { timeout: 10000 }); const wishStatus = page.locator('.wish-status, .status-tag'); if (await wishStatus.isVisible()) { const statusText = await wishStatus.textContent(); expect(statusText).toMatch(/已拒绝|已关闭/); } }); // ===== 场景9:孩子取消心愿 ===== test('[场景9] 孩子取消心愿 - 期望心愿被取消', async ({ page }) => { // ========== Given:进入我的心愿 ========== await page.goto('/#/pages/wishes/my'); await page.waitForLoadState('networkidle'); // ========== Step 1:选择心愿 ========== await page.waitForSelector('.wish-item', { timeout: 10000 }); const wishItem = page.locator('.wish-item').first(); // 点击取消按钮 const cancelBtn = wishItem.locator('.cancel-btn, .cancel-wish-btn'); if (await cancelBtn.isVisible()) { await cancelBtn.click(); // 确认取消 await page.waitForSelector('.confirm-dialog, .van-dialog', { timeout: 5000 }); await page.click('.confirm-dialog .van-button--primary, .confirm-cancel-btn'); // ========== Then:心愿被取消 ======== await page.waitForTimeout(1000); // 验证心愿不在列表中或状态变更 const statusTag = wishItem.locator('.status-tag'); if (await statusTag.isVisible()) { const statusText = await statusTag.textContent(); expect(statusText).toMatch(/已取消|已关闭/); } } }); });