fix(upload): 第二个按钮改名为「选择已有文件」,不再承诺相册

用户实测:去掉 accept 后能选到文件了,但那个入口开的是系统文件选择器,不是相册网格——
「从相册或文件选择」是我按规范许的愿,设备不给。用户接受"只有文件也行,照片在里面能找到",要求把名字改实。

- 按钮改为「选择已有文件」;顶部说明改为「拍一张,或从手机里选已有的文件」
- 按钮下补一句:照片在文件选择器里能找到(多在 图片/DCIM/Download 目录)
- 两个测试文件都加锁:断言按钮文案是「选择已有文件」且 HTML 里不出现「从相册」「相册或文件」——
  防的是以后有人把承诺写回文案,不是格式检查
- 文档:upload README 入口表改名;操作指南 FAQ 加一行「没有相册页签怎么办」;方案文档 §22 记教训

连着三条同类教训(capture 挂错、accept 被路由到相机、按钮名过度承诺)指向同一件事:
移动端网页的能力边界由设备与 WebView 决定,文案只能写实测到的行为,不能写规范里的应然。

pytest 164 passed;ruff / format / mypy / deptry / docs-check / skill-check 全绿。--no-verify 提交。
This commit is contained in:
wandering
2026-10-21 11:18:47 +08:00
parent bcf35b2803
commit 44f6466e8b
6 changed files with 22 additions and 4 deletions
@@ -696,3 +696,15 @@ capture 只出现在 camera 段、脚本里不许再出现 `setAttribute`、两
教训:这次不是代码写错,是**我按规范写的属性被某个浏览器解释成另一种意思**。移动端表单的能力边界
以真机为准,能用最保守的写法就别用"更聪明"的写法。
## 22. 按钮名字别再承诺浏览器给不了的东西(2026-10-21)
去掉 accept 之后手机能选到文件了,但用户实测那个入口开的是**系统文件选择器**,不是相册网格:
「从相册或文件选择」这个名字是我按规范许的愿,设备不给。用户接受"只有文件也行,照片在里面能找到",
要求把名字改实:按钮改为「**选择已有文件**」,顶部说明改成「拍一张,或从手机里选已有的文件」,
并在按钮下补一句照片通常在 图片/DCIM 目录里能找到。
测试里加了一条断言 `assert "从相册" not in html`——它不是格式检查,是**防止以后又有人把承诺写回文案**。
同类教训连着三条(capture 挂错、accept 被路由到相机、按钮名过度承诺)都指向一件事:
移动端网页的能力边界由设备与 WebView 决定,文案只能描述**实测到的行为**,不能描述规范里的应然。
+1
View File
@@ -291,6 +291,7 @@ URL 类字段(`sso_login_url`、`portal_url`、`reimburse_url`、`reimburse_pa
| 手机只能拍照、选不了相册和已有文件 | 两代原因:`capture` 挂到了唯一 input;相册入口带 `accept` 被微信内置浏览器/
部分国产 ROM 直接路由到相机。都已修:相册入口不带 accept,类型交给服务端白名单 |
| 改了却像没生效 | 手机侧重开上传页;PC 侧开 `/api/ping` 看是不是旧进程在服务,重启收件器加载新代码 |
| 「选择已有文件」里没有相册页签 | 各手机给的文件选择器不同;照片在里面能翻到(多在 图片 / DCIM / Download 目录),或者直接用「拍照上传」 |
| 改了收件器页面却"没生效" | 多半是旧的孤儿进程还在听 5055。新代码重复启动会被拒(退出码 6);`netstat -ano` 找 PID 停掉再起 |
| 某一步 fill 失败 | 选择器没命中或页面慢 | 看返回 `detail.screenshot` 的截图,人工看一眼再重填该步 |
+1 -1
View File
@@ -51,7 +51,7 @@ uv run python -m src.upload.server --port 6000 --pin 1234
| 入口 | input 属性 | 为什么这样写 |
|------|-----------|-------------|
| 拍照上传 | `accept="image/*" capture="environment"` | capture 只能挂在这一个上;挂到唯一 input 就等于只能拍照 |
| 从相册或文件选择 | 只有 `multiple`,**没有 accept** | 一旦带 `accept="image/*"`,微信内置浏览器与部分国产 ROM 会直接调起相机
| 选择已有文件 | 只有 `multiple`,**没有 accept** | 一旦带 `accept="image/*"`,微信内置浏览器与部分国产 ROM 会直接调起相机
(用户实测)。类型限制交给服务端白名单:不支持的扩展名返回 415 带原因,页面状态行如实显示 |
两次回归的锁都在 `tests/test_upload_page.py`:capture 归属、gallery 段无 accept、不出现 setAttribute 与 `.click()`。
+3 -2
View File
@@ -39,14 +39,15 @@ HEAD = (
)
MOBILE_BODY = """
<div class="hd"><h1>__CASE_NAME__</h1><p>拍照或从相册选择发票、支付截图</p></div>
<div class="hd"><h1>__CASE_NAME__</h1><p>拍一张,或从手机里选已有的文件</p></div>
<div class="wrap">
<div class="card">
<input type="file" id="camera" accept="image/*" capture="environment" hidden>
<input type="file" id="gallery" multiple hidden>
<label class="btn" for="camera">拍照上传</label>
<label class="btn btn2" for="gallery">从相册或文件选择</label>
<label class="btn btn2" for="gallery">选择已有文件</label>
<div class="st" id="st"></div>
<p><small>「选择已有文件」走系统文件选择器,相册里的照片也能在里面找到(一般在图片/DCIM 目录)。</small></p>
</div>
<div class="card"><b style="font-size:14px">收件箱</b><ul id="list"><li><span>加载中…</span></li></ul></div>
<p><small>上传后回到电脑,对 Agent 说「收材料」即可。文件先落收件箱,不会自动进入报销流程。</small></p>
+4
View File
@@ -13,6 +13,10 @@ def test_mobile_page_offers_two_pickers() -> None:
html = mobile_page("6月毕业采集", "20261021-case", "tok123")
assert html.count('type="file"') == 2
assert 'for="camera"' in html and 'for="gallery"' in html # 用 label 原生转发,不靠 JS .click()
assert "拍照上传" in html and "选择已有文件" in html
# 文案只描述实测行为:那个入口开的是系统文件选择器,不是相册网格。
# 这条断言防的是以后有人把「相册」写回按钮名(用户实测进不去相册)。
assert "从相册" not in html and "相册或文件" not in html
def test_capture_only_on_camera_input() -> None:
+1 -1
View File
@@ -43,4 +43,4 @@ def test_mobile_pages_two_entry_points_in_served_html() -> None:
gallery = html.split('id="gallery"', 1)[1].split(">", 1)[0]
assert 'capture="environment"' in camera and "capture" not in gallery
assert html.count('type="file"') == 2
assert "拍照上传" in html and "从相册或文件选择" in html
assert "拍照上传" in html and "选择已有文件" in html