| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- // P58 整页外壳对比:v2.9.2 原文页(工作台)↔ 新版 `/detail/v2#workbench`
- // 比整个 <body> 的元素骨架(tag.class 序列),忽略 svg/canvas/script/style 与 .portal-full/iframe。
- import { readFileSync } from 'node:fs'
- import { fileURLToPath, pathToFileURL } from 'node:url'
- import { dirname, resolve } from 'node:path'
- import { JSDOM, VirtualConsole } from 'jsdom'
- const HERE = dirname(fileURLToPath(import.meta.url))
- const GATEWAY = process.argv[2] || 'http://106.120.102.238:28084'
- const dist = resolve(HERE, '../../../release/web')
- function skeleton(el) {
- const out = []
- const walk = (n) => {
- for (const c of Array.from(n.children)) {
- const tag = c.tagName.toLowerCase()
- if (['svg', 'canvas', 'script', 'style', 'iframe'].includes(tag)) continue
- const cls = (c.getAttribute('class') || '').split(/\s+/).filter(Boolean).sort().join('.')
- out.push(tag + (cls ? '.' + cls : ''))
- walk(c)
- }
- }
- walk(el)
- return out
- }
- // ① 基线:v2.9.2 原文页(其工作台 = 默认页签的 SPA)
- const fleet = readFileSync(resolve(HERE, '_fleet_fixture.json'), 'utf8')
- const baseState = { cache: new Map() }
- function stub(window, state) {
- window.fetch = async (input) => {
- const raw = typeof input === 'string' ? input : (input && input.url) || String(input)
- const url = raw.startsWith('http') ? raw : GATEWAY + (raw.startsWith('/') ? raw : '/' + raw)
- if (/\/api\/fleet/.test(url)) return { ok: true, status: 200, headers: new Map(), text: async () => fleet, json: async () => JSON.parse(fleet) }
- if (state.cache.has(url)) { const c = state.cache.get(url); return { ok: true, status: 200, headers: new Map(), text: async () => c, json: async () => JSON.parse(c) } }
- const r = await fetch(url); const b = await r.text(); if (r.ok) state.cache.set(url, b)
- return { ok: r.ok, status: r.status, headers: r.headers, text: async () => b, json: async () => JSON.parse(b) }
- }
- }
- const bdom = new JSDOM(readFileSync(dist + '/detail_v2.html', 'utf8'),
- { url: GATEWAY + '/detail/v2', runScripts: 'dangerously', pretendToBeVisual: true,
- virtualConsole: new VirtualConsole(), beforeParse: (w) => stub(w, baseState) })
- {
- const d = bdom.window.document
- for (let i = 0; i < 120; i++) { const r = d.getElementById('root'); if (r && r.querySelector('.page-head')) break; await new Promise((x) => setTimeout(x, 500)) }
- let last = -1, same = 0
- for (let i = 0; i < 40; i++) { const n = d.body.querySelectorAll('*').length; if (n === last) { if (++same >= 3) break } else same = 0; last = n; await new Promise((x) => setTimeout(x, 700)) }
- }
- const base = skeleton(bdom.window.document.body)
- // ② 新版:/detail/v2#workbench(构建产物真跑)
- const idx = readFileSync(dist + '/index.html', 'utf8')
- const jsFile = (idx.match(/src="([^"]+\.js)"/) || [])[1].replace(/^.*\//, '')
- const cssFile = (idx.match(/href="([^"]+\.css)"/) || [])[1].replace(/^.*\//, '')
- const code = readFileSync(dist + '/assets/' + jsFile, 'utf8')
- const css = readFileSync(dist + '/assets/' + cssFile, 'utf8')
- const ndom = new JSDOM(idx.replace('</head>', '<style>' + css + '</style></head>'),
- { url: GATEWAY + '/detail/v2#workbench', runScripts: 'outside-only', pretendToBeVisual: true, virtualConsole: new VirtualConsole() })
- {
- const w = ndom.window
- w.fetch = async (u) => { const s = String(u); const body = s.includes('fleet') ? fleet : '{}'
- return { ok: true, status: 200, json: async () => JSON.parse(body), text: async () => body } }
- try { w.eval(code) } catch (e) { console.log('EVAL_ERR ' + String(e).slice(0, 120)) }
- for (let i = 0; i < 24; i++) { await new Promise((x) => setTimeout(x, 500)); if (w.document.querySelector('.wb-tabs')) break }
- }
- const now = skeleton(ndom.window.document.body)
- console.log('BASE_TOTAL ' + base.length + ' · NEW_TOTAL ' + now.length)
- const n = Math.max(base.length, now.length)
- let shown = 0
- for (let i = 0; i < n && shown < 24; i++) {
- if (base[i] !== now[i]) { console.log('#' + i + ' 基线=' + (base[i] || '∅') + ' · 新版=' + (now[i] || '∅')); shown++ }
- }
- // 关键件核验
- const has = (arr, s) => arr.some((x) => x === s || x.startsWith(s + '.'))
- for (const k of ['div.logo', 'div.brand', 'div.right', 'button.reselect', 'select', 'span.winrng', 'a.back-portal', 'nav.tabs', 'nav.wb-tabs', 'div.page-head', 'main', 'footer']) {
- console.log('件 ' + k + ' 基线=' + has(base, k) + ' 新版=' + has(now, k))
- }
|