/** * ================================================================ * E2E Test Authentication Helper * ================================================================ * Provides unified login for 8 roles: parent, child, teacher, * nutritionist, butler, assessor, admin, vendor * * Usage: * const { ROLES, loginAs } = require('./helpers/auth'); * const auth = await loginAs(browser, 'parent'); * console.log(auth.token, auth.userId, auth.role); * ================================================================ */ var BASE_URL = process.env.PW_BASE_URL || 'http://cfc.iwintrue.com'; // ================================================================ // ROLES - Test user credentials mapping // ================================================================ var ROLES = { parent: { phone: '13701366188', userId: 1002, loginType: 'code', familyId: 2001 }, child: { phone: '13701366189', userId: 1003, loginType: 'code', familyId: 2001 }, teacher: { phone: '13800000001', userId: 1005, loginType: 'code', familyId: 0 }, nutritionist: { phone: '13800000010', userId: 1006, loginType: 'code', familyId: 0 }, butler: { phone: '13800000020', userId: 1007, loginType: 'code', familyId: 0 }, assessor: { phone: '13800000030', userId: 1008, loginType: 'code', familyId: 0 }, admin: { phone: '13800138000', userId: 1001, loginType: 'password', familyId: 0 }, vendor: { phone: '13800138001', userId: 1009, loginType: 'code', familyId: 0 } }; // ================================================================ // loginAs - Unified authentication for Playwright E2E tests // ================================================================ /** * Login as a specific role and return auth information * @param {Browser} browser - Playwright Browser instance * @param {string} role - Role name (parent, child, teacher, etc.) * @returns {Promise<{token: string, userId: number, role: string, familyId: number}>} */ async function loginAs(browser, role) { var context = null; var page = null; try { var roleConfig = ROLES[role]; if (!roleConfig) { throw new Error('Unknown role: ' + role + '. Available roles: ' + Object.keys(ROLES).join(', ')); } context = await browser.newContext(); page = await context.newPage(); var authResult = { token: null, userId: roleConfig.userId, role: role, familyId: roleConfig.familyId }; if (role === 'admin') { // Admin login via UI - navigate to admin login page await page.goto(BASE_URL + '/admin/login', { waitUntil: 'networkidle', timeout: 30000 }); await page.waitForTimeout(2000); // Fill phone/username var inputSelectors = [ '.el-input__inner', 'input[type="text"]', 'input[placeholder*="账号"]', 'input[placeholder*="用户名"]', 'input[name="username"]', '.el-input input' ]; var inputFound = false; for (var i = 0; i < inputSelectors.length; i++) { try { var el = page.locator(inputSelectors[i]).first(); if (await el.isVisible({ timeout: 3000 })) { await el.fill(roleConfig.phone); inputFound = true; break; } } catch (e) { // continue to next selector } } if (!inputFound) { var allInputs = page.locator('input'); var count = await allInputs.count(); if (count > 0) { await allInputs.first().fill(roleConfig.phone); if (count > 1) { await allInputs.nth(1).fill('admin123'); } } } // Fill password (admin uses password login) var pwdInputs = page.locator('input[type="password"]'); if (await pwdInputs.count() > 0) { await pwdInputs.first().fill('admin123'); } else { var inputs = page.locator('input'); var inputCount = await inputs.count(); if (inputCount >= 2) { await inputs.nth(1).fill('admin123'); } } // Click login button var btnSelectors = [ '.el-button--primary', 'button[type="submit"]', '.login-btn', '.el-button:has-text("登录")', '.el-button:has-text("登 录")' ]; for (var j = 0; j < btnSelectors.length; j++) { try { var btn = page.locator(btnSelectors[j]).first(); if (await btn.isVisible({ timeout: 2000 })) { await btn.click(); break; } } catch (e) { // continue to next selector } } // Wait for login to complete try { await page.waitForURL(/\/dashboard|\/admin\//, { timeout: 20000 }); } catch (e) { await page.waitForSelector('.sidebar-container, .el-menu, .el-aside', { timeout: 15000 }); } // Read token from localStorage authResult.token = await page.evaluate(function() { return localStorage.getItem('token') || localStorage.getItem('access_token') || ''; }); } else { // Non-admin roles: try API login first, fallback to UI // Attempt 1: API login try { var apiResponse = await page.request.post(BASE_URL + '/api/auth/phone-login', { data: { phone: roleConfig.phone, code: '123456' }, headers: { 'Content-Type': 'application/json' } }); if (apiResponse.ok()) { var apiData = await apiResponse.json(); if (apiData && apiData.data && apiData.data.token) { authResult.token = apiData.data.token; // Store token in localStorage for UI consistency await page.evaluate(function(token) { localStorage.setItem('token', token); localStorage.setItem('access_token', token); }, authResult.token); } } } catch (apiErr) { // API login failed, will try UI fallback } // Fallback: UI login if API didn't work if (!authResult.token) { await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 30000 }); await page.waitForTimeout(2000); // Try to find and click login button or go to login page var loginBtnSelectors = [ 'button:has-text("登录")', 'a:has-text("登录")', '.login-btn', '[class*="login"]' ]; for (var k = 0; k < loginBtnSelectors.length; k++) { try { var loginBtn = page.locator(loginBtnSelectors[k]).first(); if (await loginBtn.isVisible({ timeout: 2000 })) { await loginBtn.click(); await page.waitForTimeout(1000); break; } } catch (e) { // continue } } // Fill phone and code var phoneInputSelectors = [ 'input[type="tel"]', 'input[placeholder*="手机"]', 'input[name="phone"]', '.el-input__inner' ]; for (var m = 0; m < phoneInputSelectors.length; m++) { try { var phoneInput = page.locator(phoneInputSelectors[m]).first(); if (await phoneInput.isVisible({ timeout: 2000 })) { await phoneInput.fill(roleConfig.phone); break; } } catch (e) { // continue } } // Fill verification code (123456 for test) var codeInputSelectors = [ 'input[placeholder*="验证码"]', 'input[placeholder*="code"]', 'input[name="code"]' ]; for (var n = 0; n < codeInputSelectors.length; n++) { try { var codeInput = page.locator(codeInputSelectors[n]).first(); if (await codeInput.isVisible({ timeout: 2000 })) { await codeInput.fill('123456'); break; } } catch (e) { // continue } } // Click submit button var submitSelectors = [ 'button[type="submit"]', '.el-button--primary', 'button:has-text("登录")', 'button:has-text("提交")' ]; for (var p = 0; p < submitSelectors.length; p++) { try { var submitBtn = page.locator(submitSelectors[p]).first(); if (await submitBtn.isVisible({ timeout: 2000 })) { await submitBtn.click(); break; } } catch (e) { // continue } } // Wait for navigation or token storage await page.waitForTimeout(3000); // Try to read token from localStorage authResult.token = await page.evaluate(function() { return localStorage.getItem('token') || localStorage.getItem('access_token') || ''; }); } } return authResult; } catch (error) { console.error('Login failed for role ' + role + ':', error.message); // Return partial result even on error return { token: null, userId: roleConfig ? roleConfig.userId : null, role: role, familyId: roleConfig ? roleConfig.familyId : null, error: error.message }; } finally { // Cleanup: close the browser context if (context) { try { await context.close(); } catch (closeErr) { // Ignore close errors } } } } // ================================================================ // Module exports (CommonJS) // ================================================================ module.exports = { ROLES: ROLES, loginAs: loginAs, BASE_URL: BASE_URL };