debug-full-instrument.spec.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /**
  2. * 全量仪表化诊断:记录 localStorage 生命周期 + 实际加载的 bundle + 崩溃点
  3. * 修复点 vs 上一版:
  4. * 1. 记录实际加载的 JS bundle 文件名(performance entries)
  5. * 2. 记录注入后 localStorage 的完整快照(boot 前)
  6. * 3. 全量 patch:getItem(带堆栈)/setItem/removeItem/clear
  7. * 4. 记录崩溃时完整的 localStorage 快照
  8. */
  9. const { test, expect } = require('@playwright/test');
  10. var BASE_URL = 'http://cfc.iwintrue.com';
  11. test('FULL INSTRUMENT: localStorage lifecycle + bundle identity', async ({ page }) => {
  12. var consoleMessages = [];
  13. var pageErrors = [];
  14. page.on('console', function (msg) { consoleMessages.push('[' + msg.type() + '] ' + msg.text().substring(0, 250)); });
  15. page.on('pageerror', function (err) { pageErrors.push(String(err) + '\nSTACK: ' + (err.stack || 'no-stack')); });
  16. // 1. 登录拿 token
  17. var loginRes = await page.request.post(BASE_URL + '/api/admin-auth/login-by-password', {
  18. data: { phone: '13800138000', password: 'admin123' },
  19. headers: { 'Content-Type': 'application/json' }
  20. });
  21. console.log('LOGIN_STATUS=' + loginRes.status());
  22. var body = await loginRes.json();
  23. console.log('LOGIN_BODY=' + JSON.stringify(body).substring(0, 400));
  24. if (body.code === 200 && body.data && body.data.token) {
  25. var tok = body.data.token, role = body.data.role, roles = body.data.roles || [body.data.role], adminId = body.data.adminId;
  26. console.log('INJECT_RAW token.len=' + String(tok).length + ' role=' + role + ' roles=' + JSON.stringify(roles) + ' adminId=' + adminId);
  27. // addInitScript #1:注入 + 全量 storage patch + 快照
  28. // 注意:addInitScript 只接受一个 arg,必须打包成单个对象传参
  29. await page.addInitScript(function (auth) {
  30. var token = auth.token, role = auth.role, roles = auth.roles, adminId = auth.adminId;
  31. // 记录 boot 前快照(patch 之前)
  32. window.__preSnapshot = {
  33. token: localStorage.getItem('token'),
  34. role: localStorage.getItem('role'),
  35. roles: localStorage.getItem('roles'),
  36. adminId: localStorage.getItem('adminId')
  37. };
  38. localStorage.setItem('token', token);
  39. localStorage.setItem('access_token', token);
  40. localStorage.setItem('role', role);
  41. localStorage.setItem('roles', JSON.stringify(roles));
  42. localStorage.setItem('adminId', String(adminId));
  43. // 注入后快照
  44. window.__postSnapshot = {
  45. token: localStorage.getItem('token'),
  46. role: localStorage.getItem('role'),
  47. roles: localStorage.getItem('roles'),
  48. adminId: localStorage.getItem('adminId')
  49. };
  50. // 全量 storage patch(此时注入已完成)
  51. window.__storageCalls = [];
  52. var OGS = Storage.prototype;
  53. var origGet = OGS.getItem, origSet = OGS.setItem, origRem = OGS.removeItem, origClear = OGS.clear;
  54. OGS.getItem = function (k) {
  55. var r = origGet.call(this, k);
  56. if (k === 'roles' || k === 'role' || k === 'token') {
  57. window.__storageCalls.push({ op: 'get', key: k, val: r === null ? 'NULL' : r, stack: new Error('get').stack.split('\n').slice(2, 5).join(' | ') });
  58. }
  59. return r;
  60. };
  61. OGS.setItem = function (k, v) {
  62. window.__storageCalls.push({ op: 'set', key: k, val: String(v).substring(0, 60), stack: new Error('set').stack.split('\n').slice(2, 5).join(' | ') });
  63. return origSet.call(this, k, v);
  64. };
  65. OGS.removeItem = function (k) {
  66. window.__storageCalls.push({ op: 'remove', key: k, stack: new Error('remove').stack.split('\n').slice(2, 5).join(' | ') });
  67. return origRem.call(this, k);
  68. };
  69. OGS.clear = function () {
  70. window.__storageCalls.push({ op: 'clear-all', stack: new Error('clear').stack.split('\n').slice(2, 5).join(' | ') });
  71. return origClear.call(this);
  72. };
  73. // JSON.parse patch
  74. var origParse = JSON.parse;
  75. window.__parseLog = [];
  76. JSON.parse = function (input) {
  77. if (input === undefined || input === null || (typeof input === 'string' && input.indexOf('undefined') >= 0)) {
  78. window.__parseLog.push({
  79. input: typeof input === 'string' ? 'STR:' + input.substring(0, 40) : String(input),
  80. stack: new Error('captured').stack
  81. });
  82. }
  83. return origParse.apply(this, arguments);
  84. };
  85. }, { token: tok, role: role, roles: roles, adminId: adminId });
  86. }
  87. // 2. 访问 dashboard
  88. await page.goto(BASE_URL + '/admin/dashboard', { waitUntil: 'domcontentloaded', timeout: 60000 });
  89. await page.waitForTimeout(10000);
  90. // 3. 实际加载的 bundle
  91. var resources = await page.evaluate(function () {
  92. return performance.getEntriesByType('resource').map(function (r) { return r.name; }).filter(function (n) { return n.indexOf('.js') >= 0; });
  93. });
  94. console.log('--- LOADED JS (' + resources.length + ') ---');
  95. resources.forEach(function (r) { console.log('JS: ' + r); });
  96. console.log('FINAL_URL=' + page.url());
  97. var appHtml = await page.evaluate(function () {
  98. var app = document.getElementById('app');
  99. return app ? app.innerHTML.substring(0, 300) : 'NO #app';
  100. });
  101. console.log('APP_HTML=' + appHtml);
  102. console.log('--- PRE SNAPSHOT ---');
  103. console.log(JSON.stringify(await page.evaluate(function () { return window.__preSnapshot; })));
  104. console.log('--- POST SNAPSHOT ---');
  105. console.log(JSON.stringify(await page.evaluate(function () { return window.__postSnapshot; })));
  106. console.log('--- STORAGE CALLS (' + (await page.evaluate(function () { return (window.__storageCalls || []).length; })) + ') ---');
  107. var calls = await page.evaluate(function () { return window.__storageCalls || []; });
  108. calls.forEach(function (c) { console.log(c.op.toUpperCase() + ' ' + c.key + (c.val !== undefined ? '=' + c.val : '') + ' || ' + c.stack); });
  109. console.log('--- JSON.PARSE CAPTURES (' + (await page.evaluate(function () { return (window.__parseLog || []).length; })) + ') ---');
  110. var parseLog = await page.evaluate(function () { return window.__parseLog || []; });
  111. parseLog.forEach(function (e) { console.log('PARSE input=' + e.input); console.log('PARSE stack=' + e.stack); });
  112. // 4. 崩溃时 localStorage 完整快照
  113. var snap = await page.evaluate(function () {
  114. var out = {};
  115. for (var i = 0; i < localStorage.length; i++) {
  116. var k = localStorage.key(i);
  117. out[k] = localStorage.getItem(k);
  118. }
  119. return out;
  120. });
  121. console.log('--- FINAL LS SNAPSHOT ---');
  122. console.log(JSON.stringify(snap));
  123. console.log('--- PAGE ERRORS (' + pageErrors.length + ') ---');
  124. pageErrors.forEach(function (e) { console.log('ERR: ' + e.substring(0, 800)); });
  125. console.log('--- CONSOLE (' + consoleMessages.length + ') ---');
  126. consoleMessages.forEach(function (m) { console.log('CON: ' + m.substring(0, 300)); });
  127. await page.screenshot({ path: 'test-artifacts/full-instrument.png', fullPage: true });
  128. expect(true).toBeTruthy();
  129. });