证据报告
加载时机 - 需要向他人展示结果,但文字无法直观传达,且证据来自实际屏幕时。如果实现了或修改了屏幕上可见的重大功能,即使没有要求,也从此处开始提交完成报告。 不涉及纯文档与屏幕之间流程图的场景。
目的
使用实际运行并截取的屏幕,制作一份回答一个具体问题的文档。接收者不阅读代码或提交日志。他们唯一了解的是屏幕,因此文档的可信度完全源于屏幕是实际运行结果这一事实。相比于用十条要点描述同一发现,两张并列放置的、从两个屏幕截取的相同记录的照片,更易于理解且不易被反驳。当你完成一个重大项目,却没有这份报告就简单地告知“已完成”时,接收者需要自己打开屏幕进行验证,这个成本就转移到了他们身上。
输出是一份HTML文件。接收者既可能在手机上打开它,也可能通过浏览器打印成PDF,因此模板需同时适配小屏和A4打印。不单独生成PDF或Markdown。
此技能没有固定的目录。首先确定这份文档要回答的一个问题,然后根据该问题引出对比轴和章节构成。
| 问题 | 对比轴 | 文档做的事 |
|---|---|---|
| 什么变了 | 旧代码 <-> 新代码 | 并排放置两个屏幕,证明改进 |
| 是否按规格完成,还有什么没做 | 规格书 <-> 当前屏幕 | 逐项判定,只保留不一致项 |
| 是否符合我们的原则 | 原则、检查清单 <-> 当前产品 | 将发现按原因分组,截取可显示在屏幕上的内容作为证据 |
| 条件变了会怎样 | 状态A <-> 状态B | 实际切换标志位、日期、权限进行截取 |
| 当前状态如何 | 无对比轴 | 仅凭当前屏幕概览全局 |
| 需要决定什么 | 选项 <-> 选项 | 每项都配对当前屏幕、选项和必需输入 |
通常一个任务会混合两个以上的对比轴。 在进行规格对照时,可能会出现“规格里没有,但已经存在”的情况,这就属于另一个轴的项目,需要另起一节。问题模糊,文档也会模糊。如果无法用一句话写清楚,就拆分成两份文档。
核心原则
- 屏幕必须是实际运行结果,而非模拟。 凭记忆“大概是这样”制作几乎总是错的。之前的屏幕可能包含现已删除的元素,或者问题比记忆中的更轻微或更严重。臆测“在这种条件下应该这样显示”也是错的。实际开启标志位后,屏幕显示可能与预期不同。因此,必须先实际制造出要展示的状态,再进行截取。只有这样,才能在文档中写下“这是实际运行的结果,而非模拟”,而这一句话就决定了整份文档的可信度。 -> 步骤3
- 图片旁边放置值。 图片只能说明“看起来这样”。并排放置存储的值和屏幕上的值(
data-*属性、屏幕阅读文本),就能解释“为什么看起来这样”。 -> 步骤3 - 标注在屏幕内绘制。 凭感觉输入坐标几乎总会偏移一列或一行。对于浏览器屏幕,使用文本或选择器定位元素,直接在正确位置绘制边框和编号再截取。 -> 步骤4
- 添加的说明不能覆盖被说明的目标。 将高亮标签直接放在屏幕上方几乎总会遮挡某些内容,如标题、输入框标签、相邻列的值。图片上只放编号,说明放在图注中。编号徽章很小,放置在任何位置都不会造成太大遮挡。 -> 步骤4
- 制作完成后必须用眼睛看。 工具返回成功只表示文件已写入。边框是否画在了错误的元素上、徽章是否覆盖了内容、拼接处是否重复出现了表头、链接中的图片是否损坏,这些都必须打开查看才能知道。 -> 步骤5·6
- 区分已截图和未截图的部分。 对于通过代码确认但数据中没有实例而无法在屏幕上展示的内容,单独标注为“(代码依据)”。混在一起会让人怀疑已截图部分的可信度。 -> 步骤1·3
策略
| 策略 | 触发时机 | 内容 |
|---|---|---|
| 数据和设置只读,要改必须询问 | 步骤3 | 查询以寻找实例是自由的。如果为了制造实例而需要修改开发数据库,必须在用户确认后执行,并记录修改的值以便恢复。生产数据库在任何情况下都不作为目标 |
| 截取过程中产生的更改需在文档和报告中说明 | 步骤3·步骤6 | 如登录会话创建、外部登录同意、开发数据库更改等截取过程中产生的痕迹 |
| 以便于接收者打开的形式交付 | 步骤6 | 本地路径在接收者的设备上无法打开。需发送 report.html 文件或创建链接,如果是链接,需确认实际可打开后再交付 |
| 对外发送需确认 | 步骤6 | 是发送草稿还是直接发送到Slack、邮件或客户,需先询问 |
前提
| 前提 | 确认 | 不满足时 |
|---|---|---|
| 工作影响屏幕 | 更改是否体现在用户可见的屏幕上 | 仅后端或脚本更改的工作不适用。口头报告即可 |
| 待截取的产品已启动 | 本地开发服务器或staging地址是否响应 | 启动方式遵循该仓库的文档。旧代码见 axes §时间轴 |
| 有浏览器工具 | node scripts/shot.mjs 能通过Playwright运行。其他工具只要能执行页面JS即可 | Playwright安装及其他工具的使用方法见 capture §浏览器操作 |
| 可登录 | 是否是需要登录才能看到的屏幕,是否有账号 | capture §创建登录状态 |
| Python Pillow | python3 -c "import PIL" | 无法进行拼接和裁剪 |
流程
0. 问题与轴 读取 <- variants/axes - 不同轴准备的工作不同(代码回退、数据修改、文档阅读、查找实例)。只执行所选轴的步骤
1. 项目与判定 (如果是检查轴,axes §检查轴1~2用于生成项目)
2. 结构 读取 <- steps/20-document - 项目分节依据、判定术语、摘要、图表和表格的位置
3. 截取 读取 <- steps/30-capture - 登录、合成事件、长屏、读取屏幕值时的常见卡点
4. 标注 读取 <- steps/30-capture §屏幕内标注 - 凭感觉输入坐标会偏移一列
5. 图片确认 (无打开文档。确认清单在步骤5内)
6. 输出 读取 <- steps/60-output - 模板错误会破坏小屏和打印;违反决定块规则会导致复制的片段不完整
步骤0. 问题与轴
用一句话写下。必须同时包含接收者和目的,例如“向客户解释本周的修改”、“与产品经理确认规格中还有什么未完成”。目的不同,即使相同的屏幕,强调的重点也会不同。然后,从目的表格中选择轴,并阅读 axes 中对应的章节。
完成时,应已确定一句话问题、对比轴和接收者。
步骤1. 项目与判定
不要从截取开始。先列出本份文档要涵盖的项目,并给每个项目附上参考代码和判定。此列表将成为文档的骨架,截取哪些屏幕以及标注位置也由此确定。
参考代码应按不同性质使用不同前缀。因为接收者的关注点不同。
- 按要求完成的工作:
R-1、R-2……(关心是否完成) - 过程中发现并一起修复的:
A-1、A-2……(关心为什么改动) - 规格对照中不一致的:
G-1、G-2……(关心如何处理) - 检查的发现:使用组编号
1-1、1-2……(关心同原因的分组)
判定术语在整个文档中统一使用一套(术语示例见 document §判定术语)。判定需要计数并放在第一页。 “35个已实现,3个需判断,1个缺陷”这一行字可以代表整份文档。计数的同时也能揭示列表中的空缺。
如果是跟踪工作,容易遗漏修改过的内容,需通过 git log 逐一过目;如果是规格对照,则需逐行阅读原文以填充列表。同时标记每个项目是否能在屏幕上显示(数据中是否有实例)。 -> 核心原则6
完成时,每个项目都应附有代码、判定、待截取的屏幕或“(代码依据)”。
步骤2. 结构
章节不是从目录中挑选的,而是在对项目进行分组时自然产生的。分组标准包括:
- 按屏幕分组 - 多个项目集中在一个屏幕上时。改进报告的基础
- 按判定分组 - 判定结果不同时。对照报告的基础,允许接收者优先查看“需要决定”的内容
- 按原因分组 - 发现较多且分散在多个屏幕上时。检查报告的基础
- 按项目分组 - 每个项目都是独立的决策时。决策文件的基础
无论哪种方式,前面都需要一段用一页概括全局的摘要,然后内容逐步详细。假定接收者从摘要开始阅读,并可能在任意位置停止。章节数量根据展示内容决定。不要强行填充,也不创建空章节。具体构建结构的方法和实际示例见 document。
步骤3. 截取
不同轴的准备工作不同(axes)。此处仅列出共同遵守的规则:
- 需要重叠对比的两个截图使用相同的视口、滚动位置和范围。 这样才能实现像素级对齐,以便使用滑块和差异模式查看(60-output §重叠查看)
- 对比的屏幕之间使用相同的数据。 数据不同则比较不成立。当需要展示同一事实在不同屏幕上的不同表现时,应使用同一记录截取所有屏幕
- 在截取前通过查询确认展示功能所需的数据确实存在。 如果截取了空屏幕才发现没数据,就需要重新搭建环境
- 确认登录状态后再截取。 实际曾出现因登录状态错误而截取到缺少登录元素的情况,导致误判为“不存在”。在判定“不存在”之前,需区分是真正的缺失还是当前会话未显示
- 按相同顺序操作。 将截取列表保存为一个文件,重新截取时只需运行该文件
- 视口设置得足够大,避免底部内容被截断需要重拍
- 同时读取存储的值和屏幕上的值。 -> 核心原则2
- 记录如何到达该屏幕。 路径和操作步骤稍后需要回溯时可重新进入。截取时顺便记录,无需额外成本(document §屏幕位置与深度)
操作中经常卡住的点,请参考 capture。
步骤4. 标注
对于浏览器屏幕,使用 scripts/shot.mjs 截取时会在屏幕内绘制边框和编号(-> capture §屏幕内标注)。长屏需分块截取后用 scripts/stitch.py 拼接;对于侧边栏等非重点区域,使用同样的脚本裁剪。仅对于非浏览器屏幕,使用 grid_overlay.py 读取坐标,再用 annotate.py 绘制。
- 边框只包裹需要强调的元素。框得太大,会模糊焦点
- 颜色根据不同判定使用一套方案。翻阅文档时,仅通过颜色即可了解性质。默认方案:改进前或问题点用红色,改进后用绿色;对照报告则:缺失项用红色,存在项用绿色,需判断项用橙色
- 两个对比屏幕的标注分别定位。文本长度不同会导致布局出现细微变化
- 将所有截取和标注命令收集到一个文件中。 这样,每次修改标注时都可以重新生成全部内容,之后重新截取屏幕时也只需运行该文件
步骤5. 图片确认
使用Read工具全部打开。需要确认的内容:
- 标注结果行中是否出现
NOT FOUND - 边框是否精确包裹了强调目标。是否误标了相同文字的其他位置(如其他表格中的同一列名)
- 编号徽章是否遮挡了文字或输入框
- 拼接处是否重复出现了表头或目录
- 裁剪后的上边界是否切断了半截标题。并排放置的两张图的比例是否差异过大
- 声称“不存在”的内容在屏幕上是否真的不存在。 如果是由于登录状态或条件限制而不可见,则需要推翻判定
- 图注描述的内容与图片展示的内容是否一致。是否有在同一宽度范围内隐藏了列,却写成了“重复三次”的情况
修正不一致之处后再次确认。一次性完美无瑕的情况很少见。
步骤6. HTML与交付
- 将
assets/web-report.html复制到报告文件夹中的index.html并进行填充。骨架和卡片样式见 60-output骨架。 - 如果接收者需要做决定,在其对应的证据卡片正下方放置决定块。按下固定栏上的“复制”按钮,可将所选内容打包为Markdown,粘贴到对话框中(60-output接受决定)。
- 打开并预览三种视图:宽屏、390px宽度、通过
scripts/check_print.sh提取的所有打印页面(60-output屏幕与打印)。 - 使用
inline-images.py将report.html打包成一个文件。按原文件或通过链接导出后,以接收者打开的方式实际打开确认无误(60-output交付)。
不要为每份报告修改模板中的 <style>。将宽度硬编码为px或将表格放在 .tw 之外会破坏小屏显示。 -> 核心原则5
完成后,确保 report.html 在三种视图下均无问题,可以以接收者指定的方式打开,并且发送操作等待用户确认。
常见问题
| 症状 | 原因与对策 |
|---|---|
| 开发服务器立即退出 | next dev 会读取stdin。加上 < /dev/null 可使其立即以EOF正常退出。使用 `tail -f /dev/null |
| 弹出层或下拉菜单打不开 | Radix 等组件对合成事件无响应。使用自动化工具的真实点击而非 dispatchEvent |
| 全页截取重复同一屏幕或固定表头重叠 | 工具的全页截取无法正确滚动内部区域。使用 shot.mjs 的 full 功能分块截取后拼接 |
| 拼接后的图片中目录重复出现 | 固定元素残留。只截取或裁剪出目标区间 |
| 手机上的表格或图片超出屏幕 | 表格未用 .tw 包裹,或内联 style 中使用了px宽度。仅使用模板类 |
| 标注指向了错误的表格 | 屏幕上有多个相同文字。改变 nth 或使用 exact、选择器缩小范围 |
| 截取到了错误的标签页 | 其他会话共享了浏览器守护进程。隔离会话 |
| 将已有的功能误判为不存在 | 在未登录或条件不满足的状态下截取。判定前确认登录和条件 |
| 打开链接后部分图片空白 | 简单服务器丢失了并发请求。使用 inline-images.py 打包成单个文件 |
| 图片在框架中显示很小 | 高度限制导致缩略。将纵向较长的图片改为左右两栏布局即可放大 |
| 变更过于微妙不明显 | 对比对象选错。与其展示列表屏幕中一个列名的变化,不如展示实际编辑该值的修改屏幕 |
| 表格过密难以阅读 | 改为左侧为标签、右侧为内容的项目列表。若有分组,在行左侧只放一次组标签 |
| 文档越修越长 | 新建章节后,应移除原节中包含的新章节内容 |
通用机制
| 脚本 | 功能 | 输出 |
|---|---|---|
scripts/shot.mjs | 按照拍摄列表,用Playwright打开屏幕,绘制标注后截取。长屏分块截取 | 输出文件夹中的png文件、每条标注结果一行 |
scripts/mark.js | 在页面内使用文本或选择器定位元素,绘制边框和编号。由 shot.mjs 调用,或通过eval在其他浏览器工具中使用 | 每条标注结果一行 |
scripts/stitch.py | 拼接分块截取的片段,并按名称坐标进行裁剪 | 输出文件夹中的png文件、每张图片尺寸一行 |
scripts/inline-images.py | 将HTML中的相对路径图片和脚本嵌入,打包成单个文件 | report.html,包含的文件数量和大小 |
assets/decisions.js | 保存决定块,并通过屏幕底部的复制按钮将所选内容打包为Markdown并复制 | 固定栏、localStorage |
assets/compare.js | 在 .pair[data-compare] 上附加并排、滑块、差异栏。模式由页面全局状态控制,默认是滑块。若两张图像素尺寸不同,则禁用重叠查看 | 比较栏、差异比例 |
assets/report-nav.js | 根据 h2·h3 建立左侧目录,显示分节信息(节、图片、决定、阅读时间)、已读节、剩余决定和当前位置。重新打开时支持继续阅读 | 左侧目录(窄屏时折叠)、localStorage |
scripts/serve-tailscale.sh | 在使用Tailscale的环境中,将文件夹通过tailnet内的HTTPS地址导出,并检查响应状态(可选) | URL、PID、停止命令 |
scripts/check_print.sh | 按照浏览器打印路径,将A4 PDF抽取到临时文件夹进行验证 | PDF路径、页数、每页PNG |
scripts/grid_overlay.py · scripts/annotate.py | 在非浏览器图片上叠加坐标网格,绘制框和图例 | 带注释的图片 |
使用方法和注意事项在每个脚本的头部注释中。
产出
报告文件夹一个(<日期>-<主题>/):可编辑的 index.html、图片 images/(原图在 images/raw/)、拍摄列表与裁剪坐标文件、用于交付的 report.html(包含图片的单个文件)。交付时,需同时说明链接、截取过程中的变更以及未能在屏幕上显示的项目。
在技能生态系统中的位置
- 浏览器工具 - 此技能关注的是如何使用所选工具进行截取并放入文档。如果环境中有选择工具和隔离会话的独立规则,则遵循那些规则
- 文档技能 - 不含屏幕截图的报告、提案、幻灯片,不属于此技能范畴,应由通用文档技能处理
- 文稿润色 - 如果环境中存在用于润色报告语句和屏幕布局的准则,则使用它们。在检查轴中也作为原则的参考来源