| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- /**
- * ================================================================
- * 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';
- // ================================================================
- // DB helpers - insert verification codes directly
- // (Bypasses send-code endpoint which throws when testMode=false)
- // ================================================================
- var child_process = require('child_process');
- function insertVerificationCode(phone) {
- var mysqlHost = process.env.MYSQL_HOST || '192.168.16.251';
- var mysqlUser = process.env.MYSQL_USER || 'zxyj';
- var mysqlPass = process.env.MYSQL_PASS || 'zxyj@123';
- var mysqlDb = process.env.MYSQL_DB || 'zxyj';
- var type = phone === '13800138000' ? 'admin_login' : 'login';
- try {
- child_process.execSync(
- 'mysql -h ' + mysqlHost + ' -u ' + mysqlUser + ' -p"' + mysqlPass + '" ' + mysqlDb +
- ' -e "INSERT INTO verification_codes (phone, code, type, expires_in, created_at, expires_at, used) VALUES (\'' + phone + '\', \'123456\', \'' + type + '\', 300, NOW(), \'2026-07-28 23:59:59\', 0);"',
- { stdio: 'ignore', shell: true, timeout: 10000 }
- );
- } catch (e) {
- // ignore insert failures - the API call will fail with a clear error
- }
- }
- // ================================================================
- // 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 password-based API
- // The /api/admin-auth/login-by-password endpoint is public and accepts phone+password.
- // The admin user's password hash was pre-set in the DB.
- try {
- var apiResponse = await page.request.post(BASE_URL + '/api/admin-auth/login-by-password', {
- data: {
- phone: roleConfig.phone,
- password: 'admin123'
- },
- headers: {
- 'Content-Type': 'application/json'
- }
- });
- if (apiResponse.ok()) {
- var apiData = await apiResponse.json();
- if (apiData && apiData.code === 200 && apiData.data && apiData.data.token) {
- authResult.token = apiData.data.token;
- }
- }
- } catch (apiErr) {
- // API login failed
- }
- } else {
- // Non-admin roles: try API login first, fallback to UI
- // Insert a fresh verification code directly into DB so the API login succeeds.
- // The backend's send-code endpoint throws UnsupportedOperationException when
- // testMode=false (SMS provider not configured), so we bypass it.
- insertVerificationCode(roleConfig.phone);
- // Attempt 1: API login
- try {
- var apiResponse = await page.request.post(BASE_URL + '/api/admin-auth/login', {
- data: {
- phone: roleConfig.phone,
- code: '123456'
- },
- headers: {
- 'Content-Type': 'application/json'
- }
- });
- if (apiResponse.ok()) {
- var apiData = await apiResponse.json();
- // Backend returns HTTP 200 even on business errors; check body.code === 200
- if (apiData && apiData.code === 200 && 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
- };
|