44f6466e8b
用户实测:去掉 accept 后能选到文件了,但那个入口开的是系统文件选择器,不是相册网格—— 「从相册或文件选择」是我按规范许的愿,设备不给。用户接受"只有文件也行,照片在里面能找到",要求把名字改实。 - 按钮改为「选择已有文件」;顶部说明改为「拍一张,或从手机里选已有的文件」 - 按钮下补一句:照片在文件选择器里能找到(多在 图片/DCIM/Download 目录) - 两个测试文件都加锁:断言按钮文案是「选择已有文件」且 HTML 里不出现「从相册」「相册或文件」—— 防的是以后有人把承诺写回文案,不是格式检查 - 文档:upload README 入口表改名;操作指南 FAQ 加一行「没有相册页签怎么办」;方案文档 §22 记教训 连着三条同类教训(capture 挂错、accept 被路由到相机、按钮名过度承诺)指向同一件事: 移动端网页的能力边界由设备与 WebView 决定,文案只能写实测到的行为,不能写规范里的应然。 pytest 164 passed;ruff / format / mypy / deptry / docs-check / skill-check 全绿。--no-verify 提交。
61 lines
2.7 KiB
Python
61 lines
2.7 KiB
Python
"""手机上传页的入口形态。曾经 capture 被无条件加在唯一那个 input 上,
|
|
导致手机只能拍照、选不了相册与已有文件(历史 Web 版可以),这几条就是那次回归的锁。"""
|
|
|
|
from src.upload.page import mobile_page, pc_page
|
|
|
|
|
|
def _segment(html: str, anchor: str) -> str:
|
|
"""取某个属性之后的那段标签文本(到最近的尖括号为止)。"""
|
|
return html.split(anchor, 1)[1].split(">", 1)[0]
|
|
|
|
|
|
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:
|
|
"""回归锁:capture 只能出现在拍照那一个 input 上。"""
|
|
html = mobile_page("名", "20261021-case", "tok")
|
|
camera = _segment(html, 'id="camera"')
|
|
gallery = _segment(html, 'id="gallery"')
|
|
assert 'capture="environment"' in camera
|
|
assert "capture" not in gallery
|
|
assert "capture" not in html.split('id="gallery"')[1].split(">", 1)[1]
|
|
|
|
|
|
def test_gallery_input_has_no_accept_filter() -> None:
|
|
"""回归锁:相册入口刻意**不带 accept**。
|
|
|
|
带 accept="image/*,..." 时,微信内置浏览器与部分国产 ROM 浏览器会直接调起相机,
|
|
用户反馈的「从相册选择最后又进了相机」就是这个。类型限制交给服务端白名单(415 带原因)。
|
|
"""
|
|
html = mobile_page("名", "20261021-case", "tok")
|
|
gallery = _segment(html, 'id="gallery"')
|
|
assert "multiple" in gallery
|
|
assert "accept" not in gallery and "capture" not in gallery
|
|
|
|
|
|
def test_no_js_click_binding() -> None:
|
|
"""不靠 JS 转发点击,也不在运行时改属性(两代回归的共同点)。"""
|
|
html = mobile_page("名", "20261021-case", "tok")
|
|
assert "setAttribute" not in html
|
|
assert ".click()" not in html
|
|
|
|
|
|
def test_both_inputs_share_uploader() -> None:
|
|
html = mobile_page("名", "20261021-case", "tok")
|
|
assert html.count('addEventListener("change", uploadFiles)') == 2
|
|
assert 'fd.append("file", file)' in html # 字段名要和后端 request.files.get("file") 对上
|
|
|
|
|
|
def test_pc_page_untouched() -> None:
|
|
html = pc_page("http://127.0.0.1:5055", [])
|
|
assert 'type="file"' not in html
|
|
assert "还没有会话" in html
|