/** * 调试脚本:诊断远程 cfc-web SPA 为何不渲染侧边栏 * 运行:npx playwright test debug-admin-spa.spec.js --config=playwright.remote.config.js */ const { test, expect } = require('@playwright/test'); var BASE_URL = 'http://cfc.iwintrue.com'; test('debug SPA render - capture console + network failures', async ({ page }) => { // 1. 捕获页面 console 与错误 var consoleMessages = []; var pageErrors = []; var failedRequests = []; var responses = []; page.on('console', function (msg) { consoleMessages.push('[' + msg.type() + '] ' + msg.text()); }); page.on('pageerror', function (err) { pageErrors.push(String(err) + '\nSTACK: ' + (err.stack || 'no-stack')); }); page.on('requestfailed', function (req) { failedRequests.push(req.method() + ' ' + req.url() + ' :: ' + (req.failure() && req.failure().errorText)); }); page.on('response', function (res) { if (res.status() >= 400) { responses.push(res.status() + ' ' + res.url()); } }); // 2. 登录拿 token(与 spec 相同) var loginRes = await page.request.post(BASE_URL + '/api/admin-auth/login-by-password', { data: { phone: '13800138000', password: 'admin123' }, headers: { 'Content-Type': 'application/json' } }); console.log('LOGIN_STATUS=' + loginRes.status()); var body = await loginRes.json(); console.log('LOGIN_BODY=' + JSON.stringify(body).substring(0, 300)); if (body.code === 200 && body.data && body.data.token) { // 注意:addInitScript 只接受一个 arg,必须打包成单个对象传参 await page.addInitScript(function (auth) { var token = auth.token, role = auth.role, roles = auth.roles, adminId = auth.adminId; localStorage.setItem('token', token); localStorage.setItem('access_token', token); localStorage.setItem('role', role); localStorage.setItem('roles', JSON.stringify(roles)); localStorage.setItem('adminId', String(adminId)); // Monkey-patch JSON.parse 捕获崩溃点 var origParse = JSON.parse; window.__parseLog = []; JSON.parse = function (input) { if (input === undefined || input === null || (typeof input === 'string' && input.indexOf('undefined') >= 0)) { window.__parseLog.push({ input: String(input), stack: new Error('captured').stack, rolesRaw: localStorage.getItem('roles'), roleRaw: localStorage.getItem('role'), tokenRaw: localStorage.getItem('token') }); } return origParse.apply(this, arguments); }; }, { token: body.data.token, role: body.data.role, roles: body.data.roles || [body.data.role], adminId: body.data.adminId }); } // 捕获 localStorage 所有写入(在 app 脚本前注入) await page.addInitScript(function () { var origSet = Storage.prototype.setItem; window.__storageLog = []; Storage.prototype.setItem = function (k, v) { window.__storageLog.push(k + '=' + String(v).substring(0, 60)); return origSet.call(this, k, v); }; }); // 3. 访问 dashboard await page.goto(BASE_URL + '/admin/dashboard', { waitUntil: 'domcontentloaded', timeout: 60000 }); await page.waitForTimeout(8000); console.log('FINAL_URL=' + page.url()); console.log('TITLE=' + (await page.title())); var appHtml = await page.evaluate(function () { var app = document.getElementById('app'); return app ? app.innerHTML.substring(0, 500) : 'NO #app'; }); console.log('APP_HTML=' + appHtml); var bodyText = await page.evaluate(function () { return document.body.innerText.substring(0, 300); }); console.log('BODY_TEXT=' + bodyText); var asideCount = await page.locator('.el-aside').count(); var menuCount = await page.locator('.el-menu').count(); var loginCount = await page.locator('.login-container, #login, .login-page').count(); console.log('ASIDE_COUNT=' + asideCount + ' MENU_COUNT=' + menuCount + ' LOGIN_COUNT=' + loginCount); console.log('--- PAGE ERRORS (' + pageErrors.length + ') ---'); pageErrors.forEach(function (e) { console.log('ERR: ' + e.substring(0, 500)); }); console.log('--- FAILED REQUESTS (' + failedRequests.length + ') ---'); failedRequests.forEach(function (r) { console.log('FAIL: ' + r); }); console.log('--- HTTP>=400 RESPONSES (' + responses.length + ') ---'); responses.forEach(function (r) { console.log('RESP: ' + r); }); console.log('--- CONSOLE (' + consoleMessages.length + ') ---'); consoleMessages.forEach(function (m) { console.log('CON: ' + m.substring(0, 300)); }); await page.screenshot({ path: 'test-artifacts/debug-spa.png', fullPage: true }); // 输出 monkey-patch 捕获 var parseLog = await page.evaluate(function () { return window.__parseLog || []; }); console.log('--- JSON.PARSE CAPTURES (' + parseLog.length + ') ---'); parseLog.forEach(function (entry) { console.log('INPUT=' + entry.input); console.log('ROLES_RAW=' + entry.rolesRaw + ' ROLE_RAW=' + entry.roleRaw + ' TOKEN_RAW=' + (entry.tokenRaw || '').substring(0, 20)); console.log('STACK=' + (entry.stack || 'no-stack')); }); var storageLog = await page.evaluate(function () { return window.__storageLog || []; }); console.log('--- STORAGE WRITES (' + storageLog.length + ') ---'); storageLog.forEach(function (s) { console.log('SET: ' + s); }); expect(true).toBeTruthy(); });