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.querySelector
  • DOM.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;
}

评论

0 条
内容

提交后会显示在评论区。

这里空空如也。

Nepsyn 的四叠半赛博空间Copyright © 2026 Nepsyn. All rights reserved.