Electron 使用 CDP 自动上传文件(DOM.setFileInputFiles)
Electron 使用 Chrome DevTools Protocol 自动操控网页中的 <input type="file">,实现无人工干预的文件上传。
TL;DR
工具开发中遇到一个场景,需要 Electron 中打开一个新网页并在页面中一个文件选择框中选择文件上传。最终使用了 CDP 提供的 DOM.setFileInputFiles 能力。
类似于页面中存在一个如下的输入框,想要上传指定的文件:
<input type="file">
但浏览器存在安全限制,纯 DOM 操作无法实现改变输入框选择的文件。例如下面这些操作都会失败:
input.value = '/tmp/a.txt';
input.files = xxx;
因此要实现这个目的,通常要借助浏览器本身的接口能力,比如一些浏览器自动化工具:
- Puppeteer
- Playwright
- Selenium
但一想 Electron 本身就自带一个 Chromium 浏览器了,是否有直接使用的浏览器接口呢?
这就是本文要介绍的 Chrome DevTools Protocol(CDP)。
CDP 是什么?
CDP(Chrome DevTools Protocol)是Chrome DevTools 与浏览器通信的底层协议。
Chrome DevTools Protocol 文档例如浏览器的 DevTools 中:
- 元素(Elements)
- 网络(Network)
- 性能(Performance)
- 控制台(Console)
底层都在调用 CDP。
Electron 中的实现
window.webContents.on('dom-ready', async () => {
try {
window.webContents.debugger.attach('1.3');
const { root } = await window.webContents.debugger.sendCommand('DOM.getDocument');
const { nodeId } = await window.webContents.debugger.sendCommand(
'DOM.querySelector',
{
nodeId: root.nodeId,
selector: '#file'
}
);
await window.webContents.debugger.sendCommand(
'DOM.setFileInputFiles',
{
nodeId,
files: [traceFilePath],
}
);
} catch (error) {
// error handling...
} finally {
window.webContents.debugger.detach();
}
});
这段代码的核心流程:
页面加载完成
↓
连接 Chrome DevTools Protocol
↓
找到 <input type="file">
↓
自动设置上传文件
↓
断开调试连接
监听页面加载完成
window.webContents.on('dom-ready', async () => {})
监听 Electron 页面 DOM 加载完成,类似浏览器中的:
document.addEventListener('DOMContentLoaded', () => {})
目的是等待页面加载完成,防止找不到目标元素。
attach 调试器
window.webContents.debugger.attach('1.3');
目的是给当前页面附加 Chrome DevTools Protocol(CDP)。Electron 暴露了 webContents.debugger 可以直接调用底层 Chromium 的 DevTools API。
查找文件上传 input 元素并上传文件
对应两个 CDP 指令:
DOM.querySelectorDOM.setFileInputFiles
const { nodeId } = await window.webContents.debugger.sendCommand(
'DOM.querySelector',
{
nodeId: el.nodeId,
selector: '#file'
}
);
await window.webContents.debugger.sendCommand(
'DOM.setFileInputFiles',
{
nodeId,
files: [traceFilePath]
}
);
CDP 不直接操作 DOM 对象。而是使用 nodeId 作为 DOM 节点唯一标识。CDP 的 DOM 操作中大多数需要先获取到具体元素的 nodeId。
detach 调试器
window.webContents.debugger.detach();
在运行完成后断开调试器链接,否则 debugger 可能会一直占用。
穿透 Shadow DOM 找元素
实际的开发中遇到了需要穿透 Shadow DOM 的情况,这里提供一份简单实现:
async function visitShadowRoots(window: BrowserWindow, selectors: string[]) {
const document = await window.webContents.debugger.sendCommand('DOM.getDocument', {});
let el = document.root;
for (const selector of selectors) {
const { nodeId } = await window.webContents.debugger.sendCommand(
'DOM.querySelector',
{ nodeId: el.nodeId, selector }
);
const { node } = await window.webContents.debugger.sendCommand(
'DOM.describeNode',
{ nodeId, pierce: true }
);
el = node.shadowRoots[0];
}
return el;
}
作者
Nepsyn
发布时间
2025-01-22
许可协议
CC BY-SA 4.0
评论
这里空空如也。