bsk upload 为什么传不进文件:一次跨系统路径排查

举报
yd_268799136 发表于 2026/10/27 10:28:56 2026/10/27
【摘要】 上传命令返回成功,页面却只拿到 0 字节的文件,随后所有命令一起超时。用合成测试页定位到根因:控制端在 WSL、浏览器在 Windows,路径字符串跨不过系统边界,卡住的命令还堵死了整条会话通道。给出改用字节注入的解法。

现象

用浏览器自动化往一个上传页塞文件,命令返回 upload ok,页面也确实收到了 change 事件,看起来一切正常。但接下来所有命令都开始超时——截图超时、执行 JS 超时、连换个会话都连不上。只能杀掉浏览器重来。

一开始的判断是「文件进入 input[type=file] 后页面渲染进程被卡死」,于是试了隐藏 input、显形 input、JS 点按钮、物理鼠标点击、页面内 fetch 塞 DataTransfer、往原生文件对话框 SendKeys 填路径,六种做法全部失败,症状一模一样。

后来搭了个合成测试页,把这件事拆开,才发现方向从头就是错的。

把问题拆成两半

测试页很简单:一个 display:none 的 input[type=file],一个按钮,按钮 onclick 里调 input.click(),change 事件把文件名和字节数打到页面上。

<input id="f" type="file" style="display:none">
<button id="btn">上传</button>
<script>
document.getElementById('btn').onclick = () => document.getElementById('f').click();
document.getElementById('f').addEventListener('change', e => {
  const f = e.target.files[0];
  log(f ? f.name + ' ' + f.size + 'B' : '无文件');
});
</script>

四个实验,结果如下:

传法 结果
直接指向隐藏 input error: target element has no visible geometry
指向可见按钮,传 /tmp/x.zip 回 upload ok,页面收到 change,size = 0
指向可见按钮,传 /mnt/c/x.zip 同上,仍然 0 字节
指向可见按钮,传 C:\x.zip open upload file: No such file or directory

文件根本没进浏览器,页面拿到的是一个 0 字节的空壳。

根因:路径字符串跨不过系统边界

这套自动化工具的进程模型是:控制端(daemon)跑在 WSL,浏览器跑在 Windows。上传时会做两件事——先在 daemon 侧打开文件,再把文件路径交给浏览器的 DOM.setFileInputFiles。

问题就在第二步:路径是以字符串形式传过去的。/tmp/x.zip 在 WSL 里存在,在 Windows 上就是个不存在的路径;Chrome 打不开,于是给出一个 size = 0 的 File,而且不报错。

Windows 路径呢?daemon 侧直接 No such file or directory——它只能看见 Linux 命名空间。

两个方向都堵死:daemon 读得懂的路径 Chrome 读不懂,Chrome 读得懂的路径 daemon 读不懂。

为什么看起来像「全链路超时」

这是最有迷惑性的一点。上传命令并没有真的失败,它在等一个永远不会到来的文件句柄,于是占着会话的命令槽不放。之后每条命令都被拒绝,报的是:

error: previous session command is still running
details: session already has an unfinished command

实测:upload ok 之后第一条求值还能用,第二条起全被拒。想 session stop 也常被这句挡住,偶尔还会抛 Background execution cleanup timed out。

所以「截图超时、evaluate 超时、重连不上」这些现象,根源只有一个:一个卡住的命令堵住了整条通道。排查时如果只看超时的表象,会一路往网络、往页面性能上找,永远找不到答案。

解法:不传路径,传字节

既然路径跨不过去,就别传路径。改成把文件内容 base64 分片注入页面,在页面里还原成 Uint8Array,构造 File,塞进 input.files:

// 1. 分片注入(每片 40KB,多次调用累加)
window.__buf += 'UEsDBBQAAAAI...';

// 2. 页面里还原成 File 并触发 change
const bin = atob(window.__buf);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
const file = new File([bytes], 'x.zip', { type: 'application/octet-stream' });
const dt = new DataTransfer();
dt.items.add(file);
const input = document.querySelector('input[type=file]');
input.files = dt.files;
input.dispatchEvent(new Event('input',  { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));

这样做的好处是不经过浏览器所在的文件系统,路径问题自然消失;隐藏的 input 也能塞,不需要先让它可见。

实测:379 KB 的 zip 分 13 片注入,页面侧 file.size 与原文件字节数完全一致,change 正常触发。

顺手排除的一条路:CDP 直连

另一个思路是给浏览器加上 --remote-debugging-port=9222,用 CDP 直接调 DOM.setFileInputFiles,或者用 Playwright 的 set_input_files 传字节。

方向对,但环境不允许:Chrome 只把调试端口绑在 Windows 的 127.0.0.1,且会忽略 --remote-debugging-address=0.0.0.0。WSL 到宿主的端口被防火墙拦住,写了个 TCP 转发脚本也连不通,没有管理员权限加规则就是走不通。如果换成在浏览器所在的系统上跑控制脚本,这条路是可行的。

几条可复用的经验

  • 跨系统自动化,任何以路径形式传递的文件都要警惕,改传内容。
  • 命令「成功返回」不等于动作成功。这次 upload ok 是真的返回了,文件也是真的 0 字节。
  • 出现「所有命令都超时」时,先怀疑有命令卡在会话里,而不是网络或页面。去查命令槽状态,比抓包快得多。
  • 搭一个合成测试页复现,比在真实页面上盲试六种方法省时间得多——真实页面的干扰因素太多。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。