| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- /**
- * ================================================================
- * 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
- };
|