import { chromium } from '@playwright/test' /** * 定位 h=80 时底部那 4 个连续深色像素来自哪个元素。 * 做法:用一个只在上方有元素的地方做"逐元素排除"——逐个隐藏候选元素,看哪一条消失。 */ const BASE = process.env.PROBE_URL || 'http://localhost:5200' const browser = await chromium.launch() const page = await browser.newPage({ viewport: { width: 1400, height: 1200 } }) await page.goto(BASE, { waitUntil: 'networkidle' }) const clipSample = async (x, y, w, h) => { const shot = await page.screenshot({ clip: { x, y, width: w, height: h } }) return page.evaluate(async (dataUrl) => { const img = new Image() await new Promise((res) => { img.onload = res img.src = dataUrl }) const cv = document.createElement('canvas') cv.width = img.width cv.height = img.height const ctx = cv.getContext('2d') ctx.drawImage(img, 0, 0) const d = ctx.getImageData(0, 0, img.width, img.height).data const out = [] for (let i = 0; i < d.length; i += 4) out.push([d[i], d[i + 1], d[i + 2]]) return out }, `data:image/png;base64,${shot.toString('base64')}`) } const vec = async (x, y) => { const px = await clipSample(x, y, 1, 10) return px.map(([r, g, b]) => (r < 246 || g < 246 || b < 246 ? 1 : 0)).join('') } // 压到 80px await page.evaluate(() => { document.querySelector('[data-testid="table-wrap"]').style.height = '80px' }) await page.waitForTimeout(600) await page.locator('[data-testid="table-wrap"]').scrollIntoViewIfNeeded() await page.waitForTimeout(200) const geo = await page.evaluate(() => { const wrap = document.querySelector('[data-testid="table-wrap"]') const root = wrap.querySelector('.g3-table') const r = root.getBoundingClientRect() const inner = wrap.querySelector('.stk-table') const tbody = wrap.querySelector('.stk-tbody-main') return { left: Math.round(r.left), rootRect: { top: Math.round(r.top), bottom: Math.round(r.bottom) }, innerRect: (() => { const x = inner.getBoundingClientRect() return { top: Math.round(x.top), bottom: Math.round(x.bottom), scrollH: inner.scrollHeight, clientH: inner.clientHeight } })(), tbodyRect: (() => { const x = tbody.getBoundingClientRect() return { top: Math.round(x.top), bottom: Math.round(x.bottom) } })(), } }) console.log('=== h=80 几何 ===') console.log(JSON.stringify(geo, null, 2)) const x = geo.left + 300 const base = await vec(x, geo.rootRect.bottom - 4) console.log(`\n基线采样(root.bottom-4 起 10 行): ${base}`) // 逐元素隐藏,看哪一次让多余的线消失 const candidates = [ ['tbody 最后一行', '.stk-tbody-main tr:last-child'], ['tbody', '.stk-tbody-main'], ['thead', 'thead'], ['stk-table 根', '.stk-table'], ] for (const [name, sel] of candidates) { const v = await page.evaluate( ({ sel, y, x }) => { const wrap = document.querySelector('[data-testid="table-wrap"]') const el = wrap.querySelector(sel) if (!el) return 'not-found' const prev = el.style.visibility el.style.visibility = 'hidden' return 'hidden' }, { sel, y: geo.rootRect.bottom - 4, x }, ) if (v === 'not-found') { console.log(`隐藏 ${name}: 元素不存在`) continue } await page.waitForTimeout(150) const after = await vec(x, geo.rootRect.bottom - 4) console.log(`隐藏 ${name.padEnd(16)} → ${after} ${after !== base ? '← 变化' : ''}`) await page.evaluate((sel) => { const wrap = document.querySelector('[data-testid="table-wrap"]') const el = wrap.querySelector(sel) if (el) el.style.visibility = '' }, sel) await page.waitForTimeout(120) } await browser.close()