/** * ================================================================ * 场景:规划师绑定邀请全流程 E2E 测试 * ================================================================ * 用户故事:作为成长规划师,我希望邀请家长加入我的服务团队, * 以便建立长期服务关系并管理我的客户家庭。 * * 流程步骤: * 1. 规划师登录系统,生成绑定邀请链接 * 2. 规划师将邀请链接发送给家长 * 3. 家长点击链接,查看邀请详情并确认 * 4. 家长授权绑定,成为规划师的客户 * 5. 规划师可在客户列表中看到新绑定的家庭 * * 关键里程碑: * - [Milestone-1] 邀请链接生成成功,获得邀请凭证 * - [Milestone-2] 邀请链接验证成功,展示邀请详情 * - [Milestone-3] 家长确认绑定成功,与规划师建立关系 * - [Milestone-4] 规划师客户列表更新,可以看到新家庭 * - [Milestone-5] 解绑成功,关系终止 * ================================================================ * * 运行:npx playwright test tests/e2e/planner-bind-invite.spec.js */ const { test, expect } = require('@playwright/test'); test.describe('【场景流程】规划师绑定邀请全流程', () => { // ===== 场景1:规划师生成邀请 ===== /** * 场景:规划师登录后生成一个绑定邀请 * 角色:成长规划师 * 触发条件:规划师点击"邀请家长"按钮 */ test('[场景1] 规划师生成邀请链接 - 期望返回邀请凭证', async ({ page }) => { // ========== Given:规划师进入邀请页面 ========== await page.goto('/#/pages/guide/bind/invite'); await page.waitForLoadState('networkidle'); // ========== When:点击生成邀请 ========== await page.waitForSelector('.invite-btn, .generate-invite-btn', { timeout: 10000 }); await page.click('.generate-invite-btn, .create-invite-btn'); // ========== Then:Milestone-1 - 邀请生成成功 ======== await page.waitForSelector('.invite-token, .invite-link, .invite-success', { timeout: 10000 }); // 验证邀请凭证显示 const inviteToken = page.locator('.invite-token, .invite-code'); expect(await inviteToken.isVisible()).toBeTruthy(); // 复制按钮可用 const copyBtn = page.locator('.copy-btn, .copy-invite-btn'); expect(await copyBtn.isVisible()).toBeTruthy(); }); // ===== 场景2:家长验证邀请 ===== /** * 场景:家长点击邀请链接,系统验证邀请有效性 * 角色:家长 * 触发条件:家长打开邀请链接 */ test('[场景2] 家长验证邀请链接 - 期望展示邀请详情', async ({ page }) => { // ========== Given:家长打开邀请链接 ========== await page.goto('/#/pages/guide/bind/validate?token=invite_test_abc123'); await page.waitForLoadState('networkidle'); // ========== When:页面加载验证 ====== await page.waitForSelector('.invite-detail, .guide-info, .invite-validation', { timeout: 10000 }); // ========== Then:Milestone-2 - 邀请验证成功,展示详情 ======== // 验证规划师名称显示 const guideName = page.locator('.guide-name, .guide-nickname'); expect(await guideName.isVisible()).toBeTruthy(); // 验证服务类型显示 const serviceTypes = page.locator('.service-type, .service-tag'); const serviceCount = await serviceTypes.count(); expect(serviceCount).toBeGreaterThan(0); // 验证有效期显示 const expireTime = page.locator('.expire-time, .valid-period'); if (await expireTime.isVisible()) { const expireText = await expireTime.textContent(); expect(expireText).toMatch(/\d{4}|\d{2}:\d{2}/); } }); /** * 场景:家长使用过期或无效的邀请链接 * 角色:家长 * 触发条件:链接已过期或不存在 */ test('[场景2b] 使用无效邀请链接 - 期望返回错误提示', async ({ page }) => { // ========== Given:打开无效邀请链接 ========== await page.goto('/#/pages/guide/bind/validate?token=invalid_token'); await page.waitForLoadState('networkidle'); // ========== When:系统验证链接 ========== await page.waitForSelector('.error-tip, .invalid-invite, .invite-error', { timeout: 10000 }); // ========== Then:返回错误信息 ======== const errorTip = page.locator('.error-tip, .invalid-invite, .invite-error'); expect(await errorTip.isVisible()).toBeTruthy(); const errorText = await errorTip.textContent(); expect(errorText).toMatch(/无效|过期|不存在/); }); // ===== 场景3:家长确认绑定 ===== /** * 场景:家长查看邀请详情后,确认绑定该规划师 * 角色:家长 * 触发条件:家长点击"确认绑定"按钮 */ test('[场景3] 家长确认绑定规划师 - 期望绑定成功', async ({ page }) => { // ========== Given:进入邀请确认页面 ========== await page.goto('/#/pages/guide/bind/confirm?token=invite_valid_token'); await page.waitForLoadState('networkidle'); // 验证邀请详情已显示 await page.waitForSelector('.guide-info, .invite-detail', { timeout: 10000 }); // ========== Step 1:选择服务类型(可选)========== const serviceOption = page.locator('.service-option, .service-type-item').first(); if (await serviceOption.isVisible()) { await serviceOption.click(); } // ========== When:点击确认绑定 ========== await page.click('.confirm-bind-btn, .bind-confirm-btn'); // ========== Then:Milestone-3 - 绑定成功 ======== await page.waitForSelector('.success-tip, .bind-success, .binded', { timeout: 10000 }); // 验证绑定成功提示 const successTip = page.locator('.success-tip, .bind-success'); expect(await successTip.isVisible()).toBeTruthy(); }); /** * 场景:家长尝试重复绑定同一个规划师 * 角色:家长 * 触发条件:已经绑定过该规划师,再次确认绑定 */ test('[场景3b] 重复绑定同一规划师 - 期望返回错误提示', async ({ page }) => { // ========== Given:已绑定该规划师 ========== await page.goto('/#/pages/guide/bind/confirm?token=invite_already_bound'); await page.waitForLoadState('networkidle'); // ========== When:点击确认绑定 ========== await page.click('.confirm-bind-btn'); // ========== Then:返回错误提示 ======== await page.waitForSelector('.error-tip, .already-bound-error', { timeout: 10000 }); const errorTip = page.locator('.error-tip, .already-bound-error'); expect(await errorTip.isVisible()).toBeTruthy(); const errorText = await errorTip.textContent(); expect(errorText).toMatch(/已绑定|重复|已在/); }); // ===== 场景4:查看绑定规划师列表 ===== /** * 场景:家长查看已绑定的规划师列表 * 角色:家长 * 触发条件:家长进入"我的规划师"页面 */ test('[场景4] 家长查看绑定规划师列表 - 期望返回已绑定的规划师', async ({ page }) => { // ========== Given:进入我的规划师页面 ========== await page.goto('/#/pages/profile/my-guides'); await page.waitForLoadState('networkidle'); // ========== When:查看规划师列表 ========== await page.waitForSelector('.guide-item, .bound-guide', { timeout: 10000 }); // ========== Then:Milestone-4 - 返回已绑定规划师列表 ======== const guideItems = page.locator('.guide-item, .bound-guide'); const count = await guideItems.count(); expect(count).toBeGreaterThan(0); // 验证规划师信息显示 const firstGuide = guideItems.first(); const guideName = firstGuide.locator('.guide-name'); expect(await guideName.isVisible()).toBeTruthy(); }); // ===== 场景5:家长解除绑定 ===== /** * 场景:家长决定解除与某个规划师的绑定关系 * 角色:家长 * 触发条件:家长在规划师详情页点击"解除绑定" */ test('[场景5] 家长解除绑定规划师 - 期望解除成功并发送通知', async ({ page }) => { // ========== Given:进入规划师详情页 ========== await page.goto('/#/pages/guide/detail/1'); await page.waitForLoadState('networkidle'); // ========== Step 1:找到解除绑定按钮 ========== await page.waitForSelector('.guide-info, .guide-detail', { timeout: 10000 }); const unbindBtn = page.locator('.unbind-btn, .unbind-guide-btn'); // ========== When:点击解除绑定 ========== if (await unbindBtn.isVisible()) { await unbindBtn.click(); // 确认解除 await page.waitForSelector('.confirm-dialog, .van-dialog', { timeout: 5000 }); const confirmBtn = page.locator('.confirm-dialog .van-button--primary, .confirm-unbind'); if (await confirmBtn.isVisible()) { await confirmBtn.click(); } // ========== Then:Milestone-5 - 解绑成功 ======== await page.waitForSelector('.success-tip, .unbind-success', { timeout: 10000 }); const successTip = page.locator('.success-tip, .unbind-success'); expect(await successTip.isVisible()).toBeTruthy(); } }); // ===== 场景6:规划师查看已绑定的家庭 ===== /** * 场景:规划师查看已绑定的家庭列表 * 角色:成长规划师 * 触发条件:规划师进入"我的客户"页面 */ test('[场景6] 规划师查看绑定家庭列表 - 期望返回所有客户家庭', async ({ page }) => { // ========== Given:规划师进入客户列表 ========== await page.goto('/#/pages/guide/families'); await page.waitForLoadState('networkidle'); // ========== When:查看客户列表 ========== await page.waitForSelector('.family-item, .client-family', { timeout: 10000 }); // ========== Then:返回客户家庭列表 ======== const familyItems = page.locator('.family-item, .client-family'); const count = await familyItems.count(); expect(count).toBeGreaterThan(0); // 验证家庭信息显示 const firstFamily = familyItems.first(); const familyName = firstFamily.locator('.family-name, .family-title'); if (await familyName.isVisible()) { const nameText = await familyName.textContent(); expect(nameText.length).toBeGreaterThan(0); } }); // ===== 场景7:完整绑定流程(端到端)===== test('[场景7] 完整邀请绑定流程 - 期望全流程成功', async ({ page }) => { // ========== Step 1:规划师生成邀请 ========== await page.goto('/#/pages/guide/bind/invite'); await page.waitForLoadState('networkidle'); const generateBtn = page.locator('.generate-invite-btn, .create-invite-btn'); if (await generateBtn.isVisible()) { await generateBtn.click(); await page.waitForSelector('.invite-token, .invite-link', { timeout: 10000 }); } // 获取邀请链接 const inviteLink = page.locator('.invite-link'); let inviteUrl = ''; if (await inviteLink.isVisible()) { inviteUrl = await inviteLink.getAttribute('data-url') || await inviteLink.textContent(); } // ========== Step 2:家长验证邀请(模拟)========== await page.goto('/#/pages/guide/bind/validate?token=invite_test_flow'); await page.waitForLoadState('networkidle'); await page.waitForSelector('.guide-info, .invite-detail', { timeout: 10000 }); // ========== Step 3:家长确认绑定 ========== await page.goto('/#/pages/guide/bind/confirm?token=invite_test_flow'); await page.waitForLoadState('networkidle'); await page.waitForSelector('.guide-info', { timeout: 10000 }); const confirmBindBtn = page.locator('.confirm-bind-btn, .bind-confirm-btn'); if (await confirmBindBtn.isVisible()) { await confirmBindBtn.click(); await page.waitForSelector('.success-tip, .bind-success', { timeout: 10000 }); } // ========== Step 4:规划师查看客户列表更新 ========== await page.goto('/#/pages/guide/families'); await page.waitForLoadState('networkidle'); await page.waitForSelector('.family-item', { timeout: 10000 }); // 验证新绑定的家庭出现在列表中 const familyItems = page.locator('.family-item'); const count = await familyItems.count(); expect(count).toBeGreaterThan(0); }); });