frontend-visual-qa
Audits already-rendered web, landing-page, HTML deck/slide, browser tool/game, dashboard/admin, design-system, and desktop UIs using real-browser or native-app journeys, inspected screenshots, DOM geometry, responsive or projection viewports, and a bundled Playwright sweep. Use after UI implementation to find typography, wrapping, overlap, overflow, responsive, route, overlay, map, transient-state, data-visualization, browser-output, file-dialog, PDF/print, or Electron-shell defects, or to compare a rendered artifact with a visual reference. Do not use for greenfield UI design, extracting a design system from screenshots, general QA-program setup, or nonvisual code debugging.
1How It Works & Mechanism
The entire skill is structured around a "Rendered Evidence Tier System": Level A = real visible browser or native window driven journeys, Level B = isomorphic DevTools or project-native E2E, Level C = fresh headless automated sweeps via bundled scripts, Level D = source code, linter, build logs, and unit tests. Explicitly, lower-tier evidence must not be promoted into stronger conclusions. The bundled Playwright sweep is explicitly positioned as Level C, not a final verdict.
| C | Fresh headless mechanical sweep from the bundled script | Overflow, wrapping, images, basic layout heuristics, screenshot candidates |
The bundled scripts intentionally carry zero npm dependencies: Playwright is resolved from the **audited project's** node_modules using createRequire(audited-project/package.json) first, falling back to the skill's own directory. If Playwright is missing from the project, it does not install it; instead, it degrades gracefully and truthfully reports its absence.
createRequire(`${process.cwd()}/package.json`), // the audited project
The scan is not static analysis: the entire auditPage detection routine is injected and executed inside a real Chromium page via page.evaluate, measuring metrics via getBoundingClientRect, scrollWidth-clientWidth, getComputedStyle, and checkVisibility directly on the real layout engine before returning structured issues.
const result = await page.evaluate(auditPage, {
"Whether text is actually rendered" is evaluated character by character: visibleTextFromNode uses a Range to measure individual character bounding boxes, progressively subtracting ancestor clipping rectangles (overflow:hidden/clip); clipped characters are replaced with whitespace. Thus, --forbid and --require check truly visible text, not raw textContent.
range.setStart(node, index);
Overflow and clipping are calculated numerically rather than flagged as "suspicious elements": element-level overflowX is computed as el.scrollWidth - el.clientWidth, page-level as documentElement.scrollWidth - clientWidth, and section-level tracks both scrollWidth-clientWidth and rect.width - documentElement.clientWidth. Entries with explicit pixel values are emitted only when thresholds are exceeded.
const overflowX = el.scrollWidth - el.clientWidth;
Media defects are segmented by granular lifecycle states rather than binary pass/fail: selected, rendered, notRendered, loaded, broken, stillLoading, deferredLazy, and missingSource. Offscreen, lazy-loaded, unrequested images are isolated as deferredLazy to avoid treating unrequested media as broken.
const isDeferred = !mediaWalkRequested
"Silent degradation" is detected via falsifiable empirical measurements rather than trusting API declarations: font mode renders a probe string containing Latin characters and measures width differences against an intentionally nonexistent fallback font family; document.fonts.check() is emitted only as a reference (as it returns true even when @font-face is completely absent).
const sentinel = measure('"NoSuchFamily__PROBE__"');
The font mode yields five mutually exclusive verdicts (platformGeneric, healthy, hostProvidedOnly, declaredButUnusable, absentStackEntries), classifying only hostProvidedOnly and broken asset as defects—conflating "bundled and functional" with "never shipped" causes users to mistakenly delete viable fallbacks.
const hostOnly = custom.filter((f) => f.actuallyRenders && !f.shippedByPage);
Class mode detects unhandled silent degradations such as "entire CSS rule sets failing to match following major component library renames": referenced .ant-* class names are extracted from project CSS and checked against the installed library CSS; zero matches are flagged as dead.
Rules targeting them match nothing and fail silently — the component keeps the library's stock look.
Evidence discipline is enforced in code, not just in prompt instructions: URLs in reports are strictly sanitized and redacted (retaining only origin + /<redacted-path> + query keys + #<redacted-fragment>), and target-related sensitive tokens are masked from rendered text. Headers can only be supplied via environment variables; passing --header directly is rejected before the browser launches.
process.stderr.write('--header is disabled; use --header-env for every header value\n');
2Core Capabilities
3External Dependencies
| 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 |
4Recommended Governance & Evidence
- 本地凭据 / 浏览器配置: Reads user login cookies/sessions/keychains: --storage <playwright-storage-state.json>: Reads Playwright login state files from disk (including cookies/
- scripts/: Executes local scripts or commands: node scripts/visual_layout_audit.mjs (Main scanner: launches headless Chromium, injects auditPage
- 网络端点: Declares external network access: page.goto(--url) (visual_layout_audit.mjs) and subsequent subresource requests initiated by the page itself—limited strictly to the audited target origin
- 本地工作目录: File I/O and artifact outputs: <outDir>/<viewport>.png and <outDir>/frontend-visual-qa-report.json (containing issues,
5Second-Pass Verification
- [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 无命中),因此这些用例目前只在手动执行时生效。
6Verdict & Scenarios
48ff3a56b28549a2…