1实现原理 · 为什么它能做到
整个 skill 的骨架是「渲染证据分级制」:A=真实可见浏览器/原生窗口驱动,B=同态 DevTools 或项目自有 E2E,C=自带脚本的全新无头机械扫描,D=源码/lint/build/单测;并显式禁止把低层级证据升格成强结论。自带 Playwright 扫描被明确定位为 Level C,不是最终裁决。
| C | Fresh headless mechanical sweep from the bundled script | Overflow, wrapping, images, basic layout heuristics, screenshot candidates |
自带脚本刻意不带任何 npm 依赖:Playwright 从**被审计项目**的 node_modules 解析,先用 createRequire(被审计项目/package.json),再回落到 skill 自身目录;项目里没有 Playwright 时不是去安装,而是降级并如实上报。
createRequire(`${process.cwd()}/package.json`), // the audited project
扫描不是静态分析:整个 auditPage 检测函数被 page.evaluate 注入真实 Chromium 页面内执行,用 getBoundingClientRect / scrollWidth-clientWidth / getComputedStyle / checkVisibility 在真实排版引擎上取值后回传结构化 issues。
const result = await page.evaluate(auditPage, {
「文字有没有真的渲染出来」是逐字符判定的:visibleTextFromNode 用 Range 把每个字符单独取框,再逐层减去祖先 overflow:hidden/clip 的裁剪矩形,被裁掉的字符替换为空格;因此 --forbid/--require 检查的是真实可见文本,不是 textContent。
range.setStart(node, index);
溢出与裁切是数值计算而不是「列出可疑元素」:元素级 overflowX = el.scrollWidth - el.clientWidth,页面级用 documentElement.scrollWidth - clientWidth,区块级另算 scrollWidth-clientWidth 与 rect.width - documentElement.clientWidth 两个不同口径,超阈值才产出带 px 数值的条目。
const overflowX = el.scrollWidth - el.clientWidth;
媒体缺陷按状态拆分而不是通过/失败:selected / rendered / notRendered / loaded / broken / stillLoading / deferredLazy / missingSource,并把「首屏外 + lazy + 尚未请求」单独归入 deferredLazy,避免把没请求当成坏图。
const isDeferred = !mediaWalkRequested
找「静默降级」用可证伪的测量而不是信任 API 自述:font 模式渲染含拉丁字符的探针串,与一个故意不存在的字体族宽度做差;document.fonts.check() 只作对照输出(它在完全没有 @font-face 时也返回 true)。
const sentinel = measure('"NoSuchFamily__PROBE__"');
font 模式给五种互斥结论(platformGeneric / healthy / hostProvidedOnly / declaredButUnusable / absentStackEntries),只把 hostProvidedOnly 与 broken asset 判为缺陷——因为把「内置且正常」与「从未发布」混为一谈,会导致用户删掉本来可用的 fallback。
const hostOnly = custom.filter((f) => f.actuallyRenders && !f.shippedByPage);
class 模式检测「组件库大版本改名后整组 CSS 规则永不匹配」这类不报错的降级:从项目 CSS 抽出被引用的 .ant-* 类名,逐个到安装的库 CSS 里查是否存在,零命中即 dead。
Rules targeting them match nothing and fail silently — the component keeps the library's stock look.
证据纪律被写进代码而不只写在提示词里:报告中的 URL 一律结构化脱敏(只留 origin + /<redacted-path> + 查询键名 + #<redacted-fragment>),并把目标相关的敏感 token 从渲染文本中打码;header 只能经环境变量间接传入,原样 --header 在启动浏览器之前就被拒绝。
process.stderr.write('--header is disabled; use --header-env for every header value\n');
2核心能力
3外部依赖
| Kind | Dependency / Endpoint |
|---|---|
| package | Playwright(playwright-core / playwright / @playwright/test 三选一,从被审计项目解析) |
| cli | 本机 Chrome / Chromium / Edge 可执行文件(findChrome 的候选路径,可用 CHROME_PATH 覆盖) |
| network | 被审计目标 URL 及其子资源(唯一网络目的地,由调用者经 --url 指定) |
| cli | Node.js 运行时与内置模块(node:crypto/fs/os/path/url/module);自带回归用 node --test |
4管控建议与判定依据
- 本地凭据 / 浏览器配置: 读取用户登录 Cookie/会话/密钥环: --storage <playwright-storage-state.json>:读取磁盘上的 Playwright 登录态文件(含 cookie/
- scripts/: 执行本地脚本或命令: node scripts/visual_layout_audit.mjs(主扫描器:启动 headless Chromium,注入 auditPage
- 网络端点: 声明外部网络访问: page.goto(--url)(visual_layout_audit.mjs)与其后页面自身发起的子资源请求——唯一目的地是被审计目标 origi
- 本地工作目录: 文件读写与工件输出: <outDir>/<viewport>.png 与 <outDir>/frontend-visual-qa-report.json(含 issues、
5第二遍独立确认
- [ok] external_deps 逐条反查调用点是否真实存在 — grep 工具全量扫 scripts/:Playwright 候选数组(playwright-core / playwright / @playwright/test)、findChrome 的 process.env.CHROME_PATH、page.goto(url, { waitUntil, timeout: 30_000 })、SKILL.md 的 node --test 四处均逐字定位到文件行;网络 token 扫描只命中两处——probe 的 route.fetch({ headers: requestHeaders, maxRedirects: 0 }) 与 visual_layout_audit.mjs 用法字符串里的 http://localhost:port/ 示例,无第三方端点,无 axios / XMLHttpRequest / WebSocket / child_process / new Function。
- [discrepancy] 安全结论反例搜索:是否存在未披露的外发/降级/隐藏脚本 — 主体结论(无第三方外发、无遥测、默认只读)成立,但发现两处 SKILL.md 未在安全语境点明的代码级能力:① probe 经 route.fetch 把 --header-env 提供的凭证以请求头形式发到目标 origin(值取自 process.env,不进 argv),文档只强调『argv 只含名字不含值』,未说『值会被转发给目标站』;② probe 有 --ignore-https-errors(ignoreHTTPSErrors: has('ignore-https-errors')),会关闭该上下文的 TLS 校验——SKILL.md 只在 sweep 段落自述『TLS stays enabled except for an explicitly trusted self-signed origin』,未提 probe 侧同一开关。两者都是显式 opt-in、需调用者自己写出 flag;未发现混淆内容、后门或隐藏脚本。
- [ok] 凭证读取与文件写入面逐条复核 — credential_reads 三条、file_writes 三条均落到代码行:process.env[envName](probe)、storageState: flag('storage') || undefined、findChrome 的 CHROME_PATH/PROGRAMFILES/LOCALAPPDATA;写入侧为 outDir(默认 os.tmpdir()/frontend-visual-qa-<ISO>-<pid>)、frontend-visual-qa-report.json、截图 PNG。脚本不读 cookie 数据库、不读 keychain、不读 .env,也无删除行为。
- [ok] 文件清单 glob 全量核对(含隐藏文件) — find -type f 得 24 个文件:SKILL.md 1 + scripts 3 + tests 2 + references 7 + evals/evals.json + evals/trigger-evals.json + evals/files 13(12 HTML + static-review.png);find -name ".*" 在 skill 目录内为空(无隐藏文件、无 .env、无嵌套仓库)。与 SKILL.md『Bundled Resources』清单一一对应,没有未列出的可执行文件。
- [ok] 『实现原理』是否夸大:功能声明 vs 实际代码能力 — SKILL.md 称自带脚本为 'Playwright-powered mechanical viewport, layout, and media-state sweep with screenshots and JSON evidence',代码确为 chromium.launch + 三档视口 + 截图 + JSON 报告;report.checked 列的七项(navigation status and final URL、effective viewport and meta viewport、page and section overflow、rendered text wrapping and clipping candidates、custom interactive-role focusability candidates、rendered image request/load state and aspect candidates、page-type contract heuristics)在 auditPage 内逐条可定位到具体计算与 push。反向也不夸大:脚本在 report.notChecked 里主动列出做不到的项(真实浏览器 chrome 与打印预览、下载/剪贴板/弹窗/原生对话框、键盘旅程与焦点外观、完整 WCAG、GIS 交互、主观 parity),自述边界严于对外宣称。
- [discrepancy] SKILL.md 关于『报告只写进唯一临时运行目录』的措辞与代码是否一致 — 默认路径确实唯一(os.tmpdir()/frontend-visual-qa-<ISO 时间戳>-<pid>,且拒绝非空 outDir 以防陈旧证据),但 --out 允许任意路径,脚本层不限制它落在被审计项目内;SKILL.md 的『Do not mutate the audited project merely to run the sweep』只是提示词层约束,代码不强制。属措辞与实现的轻微落差,非缺陷。
- [ok] 注入面复核:被审计页面内容是否会进入可执行路径 — 四个注入浏览器的函数体全部来自 skill 自带文件(auditPage / collectAttentionInventory / primeVisibleMedia / collectScreenshotSections),页面内容只作为数据被 Range 测量、正则匹配、结构描述,不参与代码执行;无 eval、无 new Function、无 child_process,也没有把页面文本拼进 argv/shell 的路径(probe 的 --select 只交给 page.locator)。残余风险在模型侧(页面可埋针对审计 agent 的指令文本,本 skill 无过滤)与用户输入侧(--forbid/--require 正则无回溯上限)。
- [ok] evals/ 的定位:是否确为自带评测夹具,能否在本地复跑出分数 — evals/evals.json 是 21 条 prompt + expectations 的行为回归集(12 条带 fixture 文件),evals/trigger-evals.json 是 24 条 should_trigger 路由集(14 true / 10 false),schema 与仓库内 daymade-skill/skill-creator/references/eval_pipeline_schemas.md 一致,且 skill-creator 的打包逻辑默认排除 root evals/(自述 'excluded by default as a development asset'),与 SKILL.md 的『excluded from packaged runtime content』吻合。但 skill 目录内没有 runner/评分器,仓库 .github/workflows/ci.yml 与 benchmark.yml 也未引用本 skill 的 tests 或 evals(grep 无命中),因此这些用例目前只在手动执行时生效。
6结论
48ff3a56b28549a2…