文件上传自动化的三种形态,以及一个让我误判三天的意外
给平台做自动化,文件上传是绕不过去的一环。看起来都是"点一个按钮传个文件",实际在 DOM 里有三种完全不同的形态,每种的处理方式都不一样。我在第三种上栽了个大跟头。
## 形态一:可见的 input[type=file]
最简单的一种。元素就在 DOM 里,Playwright 直接写:
```python
page.locator('input[type="file"]').set_input_files(path)
```
如果元素是隐藏的(`display:none` 或者宽度 0),`set_input_files` 依然有效——它不要求元素可见。这点很多人不知道,会去点外层按钮触发文件框,反而多绕一步。
判断方式:
```python
if page.locator('input[type="file"]').count():
page.locator('input[type="file"]').first.set_input_files(path)
```
## 形态二:真·系统文件选择框
元素不在 DOM 里,点按钮会弹系统对话框。这时候必须用 `expect_file_chooser` 接住:
```python
with page.expect_file_chooser(timeout=15000) as fc_info:
page.locator('button:has-text("上传作品")').first.click()
fc_info.value.set_files(path)
```
关键点:**`expect_file_chooser` 必须在点击之前开**。写在点击之后永远等不到——因为事件已经发生过了,而且 Playwright 会一直挂在那里等下一个(永远不来的)事件,最后超时。
我第一次写反了顺序,超时 12 秒才报错,还以为是选择器不对。
## 形态三:上传后页面自动提交(我误判了三天的那种)
赛事作品提交那个页面,我按形态一的方式 `set_input_files` 之后,又去点"提交"按钮,一直点不到——因为**页面上根本没有"提交"按钮**。
当时的输出是这样的:
```
file set
请选择阶段组 -> value=大赛
请选择阶段 -> value=大赛
btns=["上传作品"] files=[null]
```
`files=[null]` 说明 input 的 files 已经被清空了,`btns` 里只有"上传作品",没有提交按钮。我判断"文件没挂上、提交失败",然后反复重试了两天。
第三天我截了张全页图才看见真相:
```
作品名称 阶段 提交时间 作品分数
2048-game_...zip 大赛 2026-10-25 01:05:51 评分中
```
**早已提交成功了。** `set_input_files` 之后页面自己就把提交做完了,input 被清空是提交成功的表现,不是失败。
这里有两个教训:
**一、`files=[null]` 可能是"已消费"而不是"没挂上"。** 单看这个状态无法区分,需要看页面上的结果列表。
**二、操作完先 dump 一次页面状态再决定下一步。** 我当时是"操作→立刻判断下一步该点哪",跳过了"先看看现在什么状态"这一步。加上这一步只需要一次 `page.inner_text("body")`,能省掉几天的误判。
修正后的写法:
```python
page.locator('input[type="file"]').first.set_input_files(ZIP)
page.wait_for_timeout(8000)
# 先看结果,别急着点下一步
body = page.inner_text("body")
if "提交时间" in body or "评分中" in body:
return "已提交"
# 只有在确实需要时,才去找提交按钮
```
## 一个配套的坑:文件名即业务规则
上传的文件名有时候参与业务校验。那个赛事的规则是:
> 所有参赛作品需打包为 zip,命名规范:作品名称+队伍名称+版本号+联系方式
我一开始随便起了个名字,虽然传上去了,但为了合规还是重新打了一个:
```
2048-game_hw2048team_v1.1_2756.zip
↑作品名 ↑队伍 ↑版本 ↑联系方式后四位
```
这类规范建议**在打包阶段就按规则命名**,别等上传后改。有些平台会在服务端校验文件名,不符合直接拒。
## 还有个细节:zip 的目录结构
详情包的校验通常要求特定结构:
```
README.md ← 必须在根
resources/ ← 资源目录
```
打 zip 时如果用 `Compress-Archive -Path "$stage\*"`,展开后是"根目录下直接是内容",正确。如果用 `-Path $stage`(不带 `\*`),会变成"根目录下多套一层父目录",服务端校验就会报结构非法。
```powershell
Compress-Archive -Path "$stage\*" -DestinationPath $out # 对
Compress-Archive -Path $stage -DestinationPath $out # 错(多一层)
```
这个差异在本地解压看不出来(都能解压),只有服务端校验才暴露。
## 小结
| 形态 | 识别方式 | 处理 |
|---|---|---|
| 可见/隐藏 input | `input[type=file]` 存在 | `set_input_files` 直接写 |
| 系统文件框 | DOM 里没有 file input | `expect_file_chooser` 包住点击 |
| 上传即提交 | 没有提交按钮,input 被清空 | 传完先 dump 页面看结果 |
第三条最值得记:**自动化的常见失误不是"点错了",而是"点完后没确认状态就继续点"。** 每步之间插一次状态 dump,成本几乎为零,收益是把"误判三天"变成"三秒发现"。
- 点赞
- 收藏
- 关注作者
评论(0)