/** * 详情页接收行/发送行 视觉回归校验(headless Chrome) * * 做法:直接读取真实 detail.wxss(rpx→px 换算) + 按 detail.wxml 结构生成 DOM, * 并把小程序基础库的 button 默认样式(含 wx-button:not([size=mini]){width:184px}) * 一起注入 → 得到的布局与真机同源,可直接量出「上传离线是否被挤出屏幕」。 * * 用法:node tools/detail-layout-check.js (生成 /tmp/detail-layout.html) */ const fs = require('fs'); const path = require('path'); const BASE = path.join(__dirname, '../miniprogram'); const DEVICE_W = 390; // iPhone 12/13/14 逻辑宽 const RATIO = DEVICE_W / 750; // 1rpx -> px (0.52) // 1) 真实 wxss:rpx → px(保持其余原样,含选择器与特性权重) const rawCss = fs.readFileSync(path.join(BASE, 'pages/device/detail/detail.wxss'), 'utf8'); const css = rawCss.replace(/(-?\d*\.?\d+)rpx/g, (_, n) => `${(parseFloat(n) * RATIO).toFixed(3)}px`); // 2) 基础库 button 默认样式(从 WAWebview.js 原样抄,选择器 wx-button → button 等价替换) const BASE_CSS = ` button{-webkit-tap-highlight-color:transparent;background-color:#f8f8f8;border-radius:5px;box-sizing:border-box;color:#000;cursor:pointer;display:block;font-size:18px;line-height:2.55555556;overflow:hidden;padding-left:14px;padding-right:14px;position:relative;text-align:center;text-decoration:none;border:none} button:not([size=mini]){margin-left:auto;margin-right:auto;width:184px} button::after{content:" ";position:absolute;top:0;left:0;width:200%;height:200%;border:1px solid rgba(0,0,0,.2);border-radius:10px;box-sizing:border-box;transform:scale(.5);transform-origin:0 0} button[disabled]:not([type]){background-color:#fafafa;color:rgba(0,0,0,.18)} `; // 3) 按 detail.wxml 结构写 DOM(含 {{}} 取值) const DATA = { device: { serialNumber: 'BT_ECB0D1002F84', deviceId: 'EC:B0:D1:00:2F:84', enabled: true }, bleConnected: true, mqttConnected: true, canRetryBle: false, canRetryMqtt: false, displayList: [], stats: { received: 0, uploaded: 1, failed: 0 }, offlineCount: 128, autoScroll: true, recvHex: true, logFollowing: true, lastError: null, measurePointId: '371883', measurePointIdValid: true, collectMode: 1, modeManual: true, canFlush: true, canSend: true, sendHex: true, sendInput: '', }; const ev = (expr) => { try { return new Function('d', `with(d){return (${expr})}`)(DATA); } catch { return `?${expr}`; } }; const bind = (s) => s.replace(/\{\{([^}]+)\}\}/g, (_, e) => ev(e.trim())); const html = `