feat: 重建 Bell GoAdmin 产品骨架 (#62)
This commit is contained in:
@@ -0,0 +1,499 @@
|
||||
/**
|
||||
* go-admin-ui Navigation and Content Rendering Validation
|
||||
*
|
||||
* Verifies the following fixes:
|
||||
* 1. permission.js loadView: require.context now extracts .default (fixes component loading)
|
||||
* 2. main.js: registers both `this.getDicts` and `this.$getDicts` (fixes 107 usage sites)
|
||||
* 3. main.js: registers both `Pagination` and `AppPagination` component names
|
||||
*
|
||||
* Auth: JWT token pre-obtained via captcha-solving, injected via context.addCookies()
|
||||
* before page navigation, so Vue router guard reads the cookie correctly.
|
||||
*/
|
||||
|
||||
import { test, expect, Page, BrowserContext } from '@playwright/test';
|
||||
|
||||
const BASE_URL = 'http://localhost:9527';
|
||||
|
||||
// Set via an ephemeral test account and environment variable when running the test.
|
||||
const ADMIN_TOKEN = process.env.BELL_E2E_TOKEN || '';
|
||||
|
||||
// Setup authenticated browser context by adding cookie before first navigation
|
||||
async function setupAuth(context: BrowserContext): Promise<boolean> {
|
||||
if (!ADMIN_TOKEN) {
|
||||
console.log('No ADMIN_TOKEN provided');
|
||||
return false;
|
||||
}
|
||||
|
||||
await context.addCookies([{
|
||||
name: 'Bell-Admin-Token',
|
||||
value: ADMIN_TOKEN,
|
||||
domain: 'localhost',
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
}]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
test.describe('go-admin-ui Navigation and Content Rendering Validation', () => {
|
||||
|
||||
test('01 - Initial page load redirects to login', async ({ page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') errors.push(msg.text());
|
||||
});
|
||||
|
||||
await page.goto(BASE_URL, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const url = page.url();
|
||||
const title = await page.title();
|
||||
const bodyLen = (await page.locator('body').innerHTML()).length;
|
||||
|
||||
console.log('URL:', url);
|
||||
console.log('Title:', title);
|
||||
console.log('Body length:', bodyLen, 'chars');
|
||||
|
||||
expect(url).toContain('login');
|
||||
expect(title).toContain('Bell');
|
||||
expect(bodyLen).toBeGreaterThan(10000);
|
||||
|
||||
const jsErrors = errors.filter(e =>
|
||||
!e.includes('net::ERR') &&
|
||||
!e.includes('Failed to load resource') &&
|
||||
!e.includes('ERR_CERT')
|
||||
);
|
||||
console.log('JS errors on login page:', jsErrors.length);
|
||||
jsErrors.forEach((e, i) => console.log(` ${i + 1}. ${e}`));
|
||||
|
||||
await page.screenshot({ path: 'test-results/01-initial-load.png', fullPage: true });
|
||||
console.log('Screenshot: test-results/01-initial-load.png');
|
||||
});
|
||||
|
||||
test('02 - Login page form elements and captcha rendering', async ({ page }) => {
|
||||
await page.goto(BASE_URL, { waitUntil: 'networkidle' });
|
||||
await page.waitForURL(/login/, { timeout: 10000 });
|
||||
|
||||
// Note: both username and captcha inputs have name="username" (bug in source)
|
||||
const usernameInput = page.locator('input[placeholder="用户名"]');
|
||||
const passwordInput = page.locator('input[name="password"]');
|
||||
const codeInput = page.locator('input[placeholder="验证码"]');
|
||||
const loginBtn = page.locator('button').filter({ hasText: /登/ }).first();
|
||||
const captchaImg = page.locator('.login-code img');
|
||||
|
||||
await expect(usernameInput).toBeVisible({ timeout: 5000 });
|
||||
await expect(passwordInput).toBeVisible({ timeout: 5000 });
|
||||
await expect(codeInput).toBeVisible({ timeout: 5000 });
|
||||
await expect(loginBtn).toBeVisible({ timeout: 5000 });
|
||||
|
||||
const captchaCount = await captchaImg.count();
|
||||
console.log('Captcha image count:', captchaCount);
|
||||
if (captchaCount > 0) {
|
||||
const src = await captchaImg.getAttribute('src');
|
||||
const isBase64 = src?.startsWith('data:image/');
|
||||
console.log('Captcha rendered as base64:', isBase64);
|
||||
expect(isBase64).toBeTruthy();
|
||||
}
|
||||
|
||||
console.log('PASS: All login form elements present and functional');
|
||||
await page.screenshot({ path: 'test-results/02-login-form.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('03 - Dashboard layout (with auth token)', async ({ page, context }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') errors.push(msg.text());
|
||||
});
|
||||
|
||||
const authOk = await setupAuth(context);
|
||||
if (!authOk) {
|
||||
console.log('SKIP: No ADMIN_TOKEN provided');
|
||||
console.log('Run with: ADMIN_TOKEN=<token> npx playwright test');
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate directly to dashboard (cookie was set before this)
|
||||
await page.goto(`${BASE_URL}/#/dashboard`, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const url = page.url();
|
||||
console.log('Dashboard URL:', url);
|
||||
const isDashboard = url.includes('dashboard') || (!url.includes('login'));
|
||||
console.log('On dashboard (not login):', isDashboard);
|
||||
|
||||
// Check layout elements
|
||||
const hasSidebar = await page.locator('.sidebar-container').count() > 0;
|
||||
const hasNavbar = await page.locator('.navbar').count() > 0;
|
||||
const hasAppMain = await page.locator('.app-main').count() > 0;
|
||||
const hasTagsView = await page.locator('.tags-view-container').count() > 0;
|
||||
|
||||
console.log('=== Dashboard Layout ===');
|
||||
console.log('Sidebar (.sidebar-container):', hasSidebar);
|
||||
console.log('Navbar (.navbar):', hasNavbar);
|
||||
console.log('App main (.app-main):', hasAppMain);
|
||||
console.log('Tags view:', hasTagsView);
|
||||
|
||||
await page.screenshot({ path: 'test-results/03-dashboard.png', fullPage: true });
|
||||
console.log('Screenshot: test-results/03-dashboard.png');
|
||||
|
||||
expect(isDashboard).toBeTruthy();
|
||||
expect(hasSidebar).toBeTruthy();
|
||||
|
||||
const jsErrors = errors.filter(e =>
|
||||
!e.includes('net::ERR') && !e.includes('Failed to load resource') && !e.includes('ERR_CERT')
|
||||
);
|
||||
if (jsErrors.length > 0) {
|
||||
console.log('JS errors on dashboard:', jsErrors);
|
||||
}
|
||||
});
|
||||
|
||||
test('04 - System management menu expansion', async ({ page, context }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') errors.push(msg.text());
|
||||
});
|
||||
|
||||
const authOk = await setupAuth(context);
|
||||
if (!authOk) {
|
||||
console.log('SKIP: No ADMIN_TOKEN provided');
|
||||
return;
|
||||
}
|
||||
|
||||
await page.goto(`${BASE_URL}/#/dashboard`, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// List all menu items
|
||||
const menuItems = await page.locator('.el-menu-item, .el-sub-menu__title').allTextContents();
|
||||
console.log('Menu items found:', menuItems.length);
|
||||
console.log('Menu items:', menuItems.slice(0, 15).map(t => t.trim()).filter(t => t));
|
||||
|
||||
// Find and click 系统管理
|
||||
const sysMenu = page.locator('.el-sub-menu__title').filter({ hasText: '系统管理' }).first();
|
||||
const sysMenuCount = await sysMenu.count();
|
||||
console.log('系统管理 sub-menu found:', sysMenuCount > 0);
|
||||
|
||||
if (sysMenuCount > 0) {
|
||||
await sysMenu.click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const subItems = await page.locator('.el-menu-item').allTextContents();
|
||||
console.log('Sub-menu items after expansion:', subItems.slice(0, 10).map(t => t.trim()).filter(t => t));
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'test-results/04-system-menu-expanded.png', fullPage: true });
|
||||
console.log('Screenshot: test-results/04-system-menu-expanded.png');
|
||||
|
||||
const jsErrors = errors.filter(e =>
|
||||
!e.includes('net::ERR') && !e.includes('Failed to load resource') && !e.includes('ERR_CERT')
|
||||
);
|
||||
if (jsErrors.length > 0) {
|
||||
console.log('JS errors:', jsErrors);
|
||||
}
|
||||
});
|
||||
|
||||
test('05 - User management page: content and fix verification', async ({ page, context }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') errors.push(msg.text());
|
||||
});
|
||||
|
||||
const authOk = await setupAuth(context);
|
||||
if (!authOk) {
|
||||
console.log('SKIP: No ADMIN_TOKEN provided');
|
||||
return;
|
||||
}
|
||||
|
||||
await page.goto(`${BASE_URL}/#/dashboard`, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Navigate through sidebar menu
|
||||
const sysMenu = page.locator('.el-sub-menu__title').filter({ hasText: '系统管理' }).first();
|
||||
if (await sysMenu.count() > 0) {
|
||||
await sysMenu.click();
|
||||
await page.waitForTimeout(800);
|
||||
}
|
||||
|
||||
const userMenu = page.locator('.el-menu-item').filter({ hasText: '用户管理' }).first();
|
||||
const userMenuCount = await userMenu.count();
|
||||
console.log('用户管理 menu item found:', userMenuCount > 0);
|
||||
|
||||
if (userMenuCount > 0) {
|
||||
await userMenu.click();
|
||||
} else {
|
||||
await page.goto(`${BASE_URL}/#/admin/sys-user`, { waitUntil: 'networkidle' });
|
||||
}
|
||||
|
||||
// Wait 3 seconds as specified in the task
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const url = page.url();
|
||||
const hasCorrectUrl = url.includes('sys-user');
|
||||
const elCardCount = await page.locator('.el-card').count();
|
||||
const elTableCount = await page.locator('.el-table').count();
|
||||
const elTableRowCount = await page.locator('.el-table__body tr').count();
|
||||
const loadingVisible = await page.locator('.el-loading-mask:visible').count() > 0;
|
||||
|
||||
// Check for red error overlays
|
||||
const errorMsgCount = await page.locator('.el-message--error:visible').count();
|
||||
|
||||
console.log('=== User Management Page ===');
|
||||
console.log('URL:', url);
|
||||
console.log('URL contains sys-user:', hasCorrectUrl);
|
||||
console.log('el-card count:', elCardCount);
|
||||
console.log('el-table count:', elTableCount);
|
||||
console.log('el-table rows:', elTableRowCount);
|
||||
console.log('Loading spinner visible:', loadingVisible);
|
||||
console.log('Error messages visible:', errorMsgCount);
|
||||
|
||||
await page.screenshot({ path: 'test-results/05-user-management.png', fullPage: true });
|
||||
console.log('Screenshot: test-results/05-user-management.png');
|
||||
|
||||
// Fix verification - check for specific error patterns
|
||||
const criticalErrors = errors.filter(e =>
|
||||
e.includes('Cannot find module') ||
|
||||
e.includes('getDicts is not a function') ||
|
||||
e.includes('this.getDicts') ||
|
||||
e.includes('is not a function') ||
|
||||
e.includes('Cannot read properties of undefined')
|
||||
);
|
||||
|
||||
const allJsErrors = errors.filter(e =>
|
||||
!e.includes('net::ERR') && !e.includes('Failed to load resource') && !e.includes('ERR_CERT')
|
||||
);
|
||||
|
||||
console.log('\n=== Fix Verification - User Management ===');
|
||||
const fix1Pass = !criticalErrors.some(e => e.includes('Cannot find module'));
|
||||
const fix2Pass = !criticalErrors.some(e => e.includes('getDicts'));
|
||||
const fix3Pass = elTableCount > 0 || elCardCount > 0; // component rendered
|
||||
|
||||
console.log('Fix 1 (loadView .default - no "Cannot find module"):', fix1Pass ? 'PASS' : 'FAIL');
|
||||
console.log('Fix 2 (getDicts registration - no getDicts errors):', fix2Pass ? 'PASS' : 'FAIL');
|
||||
console.log('Fix 3 (Pagination/AppPagination - page renders):', fix3Pass ? 'PASS' : 'FAIL');
|
||||
console.log('el-card present:', elCardCount > 0 ? 'PASS' : 'FAIL');
|
||||
console.log('el-table present:', elTableCount > 0 ? 'PASS' : 'FAIL');
|
||||
console.log('Data loaded (rows > 0):', elTableRowCount > 0 ? 'YES' : 'NO (may be empty or still loading)');
|
||||
|
||||
if (criticalErrors.length > 0) {
|
||||
console.log('\nCRITICAL JS ERRORS:');
|
||||
criticalErrors.forEach((e, i) => console.log(` ${i + 1}. ${e}`));
|
||||
}
|
||||
|
||||
if (allJsErrors.length > 0) {
|
||||
console.log('\nAll JS errors:');
|
||||
allJsErrors.forEach((e, i) => console.log(` ${i + 1}. ${e}`));
|
||||
} else {
|
||||
console.log('\nNo JS errors on user management page');
|
||||
}
|
||||
|
||||
// Assertions
|
||||
expect(hasCorrectUrl || url.includes('admin')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('06 - Role management page: content and fix verification', async ({ page, context }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') errors.push(msg.text());
|
||||
});
|
||||
|
||||
const authOk = await setupAuth(context);
|
||||
if (!authOk) {
|
||||
console.log('SKIP: No ADMIN_TOKEN provided');
|
||||
return;
|
||||
}
|
||||
|
||||
await page.goto(`${BASE_URL}/#/dashboard`, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Navigate to role management
|
||||
const sysMenu = page.locator('.el-sub-menu__title').filter({ hasText: '系统管理' }).first();
|
||||
if (await sysMenu.count() > 0) {
|
||||
await sysMenu.click();
|
||||
await page.waitForTimeout(800);
|
||||
}
|
||||
|
||||
const roleMenu = page.locator('.el-menu-item').filter({ hasText: '角色管理' }).first();
|
||||
const roleMenuCount = await roleMenu.count();
|
||||
console.log('角色管理 menu item found:', roleMenuCount > 0);
|
||||
|
||||
if (roleMenuCount > 0) {
|
||||
await roleMenu.click();
|
||||
} else {
|
||||
await page.goto(`${BASE_URL}/#/admin/sys-role`, { waitUntil: 'networkidle' });
|
||||
}
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const url = page.url();
|
||||
const elCardCount = await page.locator('.el-card').count();
|
||||
const elTableCount = await page.locator('.el-table').count();
|
||||
const elTableRowCount = await page.locator('.el-table__body tr').count();
|
||||
|
||||
console.log('=== Role Management Page ===');
|
||||
console.log('URL:', url);
|
||||
console.log('el-card count:', elCardCount);
|
||||
console.log('el-table count:', elTableCount);
|
||||
console.log('el-table rows:', elTableRowCount);
|
||||
|
||||
await page.screenshot({ path: 'test-results/06-role-management.png', fullPage: true });
|
||||
console.log('Screenshot: test-results/06-role-management.png');
|
||||
|
||||
const criticalErrors = errors.filter(e =>
|
||||
e.includes('Cannot find module') ||
|
||||
e.includes('getDicts is not a function') ||
|
||||
e.includes('is not a function')
|
||||
);
|
||||
|
||||
const allJsErrors = errors.filter(e =>
|
||||
!e.includes('net::ERR') && !e.includes('Failed to load resource') && !e.includes('ERR_CERT')
|
||||
);
|
||||
|
||||
console.log('el-card present:', elCardCount > 0 ? 'PASS' : 'FAIL');
|
||||
console.log('el-table present:', elTableCount > 0 ? 'PASS' : 'FAIL');
|
||||
|
||||
if (criticalErrors.length > 0) {
|
||||
console.log('CRITICAL ERRORS:', criticalErrors);
|
||||
} else {
|
||||
console.log('No critical JS errors on role management page');
|
||||
}
|
||||
|
||||
if (allJsErrors.length > 0) {
|
||||
console.log('All JS errors:', allJsErrors);
|
||||
}
|
||||
});
|
||||
|
||||
test('07 - Return to dashboard via 首页 menu', async ({ page, context }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') errors.push(msg.text());
|
||||
});
|
||||
|
||||
const authOk = await setupAuth(context);
|
||||
if (!authOk) {
|
||||
console.log('SKIP: No ADMIN_TOKEN provided');
|
||||
return;
|
||||
}
|
||||
|
||||
await page.goto(`${BASE_URL}/#/dashboard`, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Navigate to role management first
|
||||
await page.goto(`${BASE_URL}/#/admin/sys-role`, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(1500);
|
||||
console.log('On role management:', page.url());
|
||||
|
||||
// Click 首页 in sidebar
|
||||
const homeMenu = page.locator('.el-menu-item').filter({ hasText: '首页' }).first();
|
||||
const homeCount = await homeMenu.count();
|
||||
console.log('首页 menu item found:', homeCount > 0);
|
||||
|
||||
if (homeCount > 0) {
|
||||
await homeMenu.click();
|
||||
await page.waitForTimeout(2000);
|
||||
} else {
|
||||
await page.goto(`${BASE_URL}/#/dashboard`, { waitUntil: 'networkidle' });
|
||||
}
|
||||
|
||||
const url = page.url();
|
||||
const isDashboard = url.includes('dashboard') || url.includes('index') || !url.includes('sys-');
|
||||
console.log('URL after returning home:', url);
|
||||
console.log('On dashboard:', isDashboard);
|
||||
|
||||
await page.screenshot({ path: 'test-results/07-back-to-dashboard.png', fullPage: true });
|
||||
console.log('Screenshot: test-results/07-back-to-dashboard.png');
|
||||
|
||||
const allJsErrors = errors.filter(e =>
|
||||
!e.includes('net::ERR') && !e.includes('Failed to load resource') && !e.includes('ERR_CERT')
|
||||
);
|
||||
if (allJsErrors.length > 0) {
|
||||
console.log('JS errors:', allJsErrors);
|
||||
}
|
||||
});
|
||||
|
||||
test('08 - Complete console error analysis (all pages)', async ({ page, context }) => {
|
||||
const loginErrors: string[] = [];
|
||||
const dashboardErrors: string[] = [];
|
||||
const userMgmtErrors: string[] = [];
|
||||
|
||||
// Collect login page errors first
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') loginErrors.push(msg.text());
|
||||
});
|
||||
|
||||
await page.goto(BASE_URL, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const jsLoginErrors = loginErrors.filter(e =>
|
||||
!e.includes('net::ERR') && !e.includes('Failed to load resource') && !e.includes('ERR_CERT')
|
||||
);
|
||||
|
||||
// Collect dashboard + user management errors if we have auth
|
||||
const authOk = await setupAuth(context);
|
||||
if (authOk) {
|
||||
page.removeAllListeners('console');
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') dashboardErrors.push(msg.text());
|
||||
});
|
||||
|
||||
await page.goto(`${BASE_URL}/#/dashboard`, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
page.removeAllListeners('console');
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') userMgmtErrors.push(msg.text());
|
||||
});
|
||||
|
||||
await page.goto(`${BASE_URL}/#/admin/sys-user`, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
const jsDashboardErrors = dashboardErrors.filter(e =>
|
||||
!e.includes('net::ERR') && !e.includes('Failed to load resource') && !e.includes('ERR_CERT')
|
||||
);
|
||||
const jsUserMgmtErrors = userMgmtErrors.filter(e =>
|
||||
!e.includes('net::ERR') && !e.includes('Failed to load resource') && !e.includes('ERR_CERT')
|
||||
);
|
||||
|
||||
const allErrors = [...jsLoginErrors, ...jsDashboardErrors, ...jsUserMgmtErrors];
|
||||
|
||||
console.log('=== Complete Console Error Analysis ===\n');
|
||||
|
||||
console.log('Login Page JS errors:', jsLoginErrors.length);
|
||||
jsLoginErrors.forEach((e, i) => console.log(` ${i + 1}. ${e}`));
|
||||
|
||||
console.log('\nDashboard JS errors:', jsDashboardErrors.length);
|
||||
jsDashboardErrors.forEach((e, i) => console.log(` ${i + 1}. ${e}`));
|
||||
|
||||
console.log('\nUser Management JS errors:', jsUserMgmtErrors.length);
|
||||
jsUserMgmtErrors.forEach((e, i) => console.log(` ${i + 1}. ${e}`));
|
||||
|
||||
// Fix-specific verification
|
||||
const hasModuleError = allErrors.some(e => e.includes('Cannot find module'));
|
||||
const hasGetDictsError = allErrors.some(e =>
|
||||
e.includes('getDicts is not a function') ||
|
||||
(e.includes('getDicts') && e.includes('not a function'))
|
||||
);
|
||||
const hasPaginationError = allErrors.some(e =>
|
||||
e.toLowerCase().includes('pagination') ||
|
||||
e.includes('AppPagination')
|
||||
);
|
||||
|
||||
console.log('\n=== Fix Verification Summary ===');
|
||||
console.log('Fix 1 - loadView .default (no "Cannot find module"):', hasModuleError ? 'FAIL' : 'PASS');
|
||||
console.log('Fix 2 - getDicts registration (no getDicts errors):', hasGetDictsError ? 'FAIL' : 'PASS');
|
||||
console.log('Fix 3 - Pagination component (no Pagination errors):', hasPaginationError ? 'FAIL' : 'PASS');
|
||||
console.log('\nTotal JS errors across all pages:', allErrors.length);
|
||||
|
||||
if (allErrors.length === 0) {
|
||||
console.log('No JS errors detected on any page!');
|
||||
}
|
||||
|
||||
await page.screenshot({ path: 'test-results/08-error-analysis-final.png', fullPage: true });
|
||||
console.log('Screenshot: test-results/08-error-analysis-final.png');
|
||||
|
||||
// All three fixes should pass
|
||||
expect(hasModuleError).toBeFalsy();
|
||||
expect(hasGetDictsError).toBeFalsy();
|
||||
expect(hasPaginationError).toBeFalsy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user