首页
/ Puppeteer DownloadPolicy 详解:用四种策略精确控制浏览器下载行为

Puppeteer DownloadPolicy 详解:用四种策略精确控制浏览器下载行为

2026-09-06 15:19:00作者:齐冠琰

在自动化场景中,页面触发的文件下载往往会打断测试流程、污染工作目录,甚至让断言无从进行。Puppeteer 通过 DownloadPolicy 类型(及其配套的 DownloadBehavior 接口)提供了对下载行为的细粒度控制:你可以让浏览器拒绝所有下载、把文件写入指定目录,或按下载 GUID 自动命名文件。读完本文,你将掌握这四种策略的语义差异、在 launch / connect / createBrowserContext 三个入口下的完整用法,以及 CDP 与 WebDriver BiDi 两条协议链路中各自的实现细节与限制。

一、DownloadPolicy 类型定义

DownloadPolicy 是一个四值联合类型,源码位于 DownloadBehavior.ts,并通过 common.ts 统一导出为公开 API:

export type DownloadPolicy = 'deny' | 'allow' | 'allowAndName' | 'default';

四个取值的具体语义如下:

策略值 行为 是否需要 downloadPath
deny 拒绝该上下文中的一切下载请求
allow 允许所有下载,文件按原名保存到指定路径
allowAndName 允许所有下载,但所有文件按下载 GUID 命名
default 使用浏览器默认行为(如可用),不主动干预

其中 allowAndName 值得单独说明:源码头注释明确写着“Setting this to allowAndName will name all files according to their download guids”(见 DownloadBehavior.ts)。当页面下载内容没有明确的 Content-Disposition 文件名、或你希望在批量下载中避免文件名冲突时,按 GUID 命名是最稳妥的做法,下载完成后再按 GUID 做重命名映射即可。

二、DownloadBehavior 接口:策略 + 路径的组合

DownloadPolicy 单独使用时只是一个字符串,真正下发给浏览器的是 DownloadBehavior 接口,完整定义见 DownloadBehavior.ts

export interface DownloadBehavior {
  /**
   * Whether to allow all or deny all download requests, or use default
   * behavior if available.
   *
   * @remarks
   * Setting this to `allowAndName` will name all files according to their
   * download guids.
   */
  policy: DownloadPolicy;
  /**
   * The default path to save downloaded files to.
   *
   * @remarks
   * Setting this is required if behavior is set to `allow` or `allowAndName`.
   */
  downloadPath?: string;
}

两个属性的约束关系需要牢记:

  • policy 是必填项,决定“允不允许下载”;
  • downloadPath 是可选的保存目录,但当 policyallowallowAndName 时必须提供——这是接口注释中明确声明的约束,也是 BiDi 协议实现中会主动校验并抛错的点(下文会看到)。

官方 API 文档可参考 DownloadPolicy 类型DownloadBehavior 接口 两个页面。

三、三个配置入口:launch、connect 与 createBrowserContext

downloadBehavior 在 Puppeteer 中有三个注入点,作用域从大到小依次为:整个浏览器默认上下文、connect 时附加的每个上下文、以及单独创建的隔离上下文。

3.1 在 puppeteer.launch() 中配置(作用于默认上下文)

downloadBehaviorLaunchOptions 的可选字段,BrowserLauncher.ts 在解析启动参数时会取出该字段并传给浏览器连接层。对应链路是 Browser.ts 中的 _attach:只要传入了 downloadBehavior,就会对默认浏览器上下文调用 setDownloadBehavior

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  headless: true,
  downloadBehavior: {
    policy: 'allow',
    downloadPath: '/path/to/downloads',
  },
});

3.2 在 puppeteer.connect() 中配置

ConnectOptions 同样包含可选的 downloadBehavior 字段(见 ConnectOptions.ts,文档参考 ConnectOptions)。在 cdp/Browser.ts 的连接逻辑中,每个新建的浏览器上下文都会应用该策略,适合连接远程浏览器实例的场景。

3.3 在 browser.createBrowserContext() 中配置(推荐)

细粒度控制的最佳实践是按上下文隔离:只有传入 downloadBehavior 的上下文受影响,其余上下文保持默认行为。BrowserContextOptions 中的 downloadBehavior 字段见 BrowserContextOptions 文档

仓库自带的测试 download.test.ts 正是用这种方式验证的——创建一个上下文、允许下载并指向临时目录,然后断言文件真实落盘:

using context = await browser.createBrowserContext({
  downloadBehavior: {
    policy: 'allow',
    downloadPath: tempDir,
  },
});
const page = await context.newPage();
await page.goto(server.PREFIX + '/download.html');
await page.click('#download');
await waitForFileExistence(join(tempDir, 'download.txt'));

deny 策略的对照测试见 download.test.ts:即使传入了 downloadPath,点击下载后文件也不会出现在目录中(waitForFileExistence 会 reject)。注意测试里 deny 时仍传了 downloadPath: '/tmp'——这在 CDP 链路下合法(deny 并不要求路径),说明 downloadPathdeny 组合不会报错,只是没有实际效果。

四、底层协议实现:CDP 与 BiDi 的差异

从源码结构看,同一个 DownloadBehavior 在两条协议链路下的落地方式并不相同,这也是理解限制条件(尤其是 allowAndName)的关键。

4.1 CDP 链路:透传给 Browser.setDownloadBehavior

CDP 实现位于 cdp/BrowserContext.ts

public async setDownloadBehavior(
  downloadBehavior: DownloadBehavior,
): Promise<void> {
  await this.#connection.send('Browser.setDownloadBehavior', {
    behavior: downloadBehavior.policy,
    downloadPath: downloadBehavior.downloadPath,
    browserContextId: this.#id,
  });
}

可以看出 CDP 侧几乎是“透传”:policy 原样作为 behavior 参数下发(因此 default 也对应 CDP 的 default 行为),路径与上下文 ID 一并提交。四个策略值在 CDP 链路上均可用。

4.2 WebDriver BiDi 链路:逐项校验,allowAndName 不受支持

BiDi 的实现在 bidi/core/Browser.tscreateUserContext 中,策略到 BiDi 命令的映射是分支式的:

if (options.downloadBehavior?.policy === 'allowAndName') {
  throw new UnsupportedOperation(
    '`allowAndName` is not supported in WebDriver BiDi',
  );
}
if (options.downloadBehavior?.policy === 'allow') {
  if (options.downloadBehavior.downloadPath === undefined) {
    throw new UnsupportedOperation(
      '`downloadPath` is required in `allow` download behavior',
    );
  }
  await this.session.send('browser.setDownloadBehavior', {
    downloadBehavior: {
      type: 'allowed',
      destinationFolder: options.downloadBehavior.downloadPath,
    },
    userContexts: [userContext],
  });
}
if (options.downloadBehavior?.policy === 'deny') {
  await this.session.send('browser.setDownloadBehavior', {
    downloadBehavior: {type: 'denied'},
    userContexts: [userContext],
  });
}

由此可以归纳出 BiDi 链路的三条规则(注意 Puppeteer 默认在 Firefox 下使用 webDriverBiDi 协议,见 BrowserLauncher.ts):

  1. allowAndName 直接抛 UnsupportedOperation:BiDi 的 browser.setDownloadBehavior 没有“按 GUID 命名”的对应能力;
  2. allowdownloadPath 缺失会抛错:这正是接口文档中“Setting this is required if behavior is set to allow or allowAndName”的运行时兜底;
  3. default 策略不发任何 BiDi 命令,即保持浏览器原生下载行为。

4.3 各策略在两种协议下的支持矩阵

policy CDP WebDriver BiDi
deny 支持(下发 behavior: 'deny' 支持(type: 'denied'
allow 支持(需 downloadPath 支持(缺 downloadPath 时抛错)
allowAndName 支持(文件按下载 GUID 命名) 不支持,抛 UnsupportedOperation
default 支持(下发 behavior: 'default' 不发送命令,保持默认

如果你的脚本要跨 Chrome / Firefox 或跨协议运行,需要针对 allowAndName 做能力探测:捕获 UnsupportedOperation,或仅在 CDP 协议下使用该策略。

五、实践建议

  • 按上下文隔离策略:优先在 createBrowserContext 级别设置,避免影响同浏览器中的其他页面(参考 download.test.ts 的组织方式);
  • 目录管理downloadPath 建议指向脚本创建的临时目录(测试用例使用 mkdtemp(join(tmpdir(), 'downloads-')) 并在使用后 rm 清理,见 download.test.ts),下载内容以页面原始文件名落盘,注意同目录下的同名覆盖问题——这也是 allowAndName 的价值所在;
  • 协议感知:Chrome 默认走 CDP,全部策略可用;Firefox 默认走 WebDriver BiDi,allowAndName 不可用,allow 必须显式给出 downloadPath(实现依据见 bidi/core/Browser.ts);
  • 相关 API 文档DownloadPolicyDownloadBehaviorConnectOptionsBrowserContextOptions 可交叉查阅完整的类型定义与默认值说明。

参考文件

登录后查看全文
热门项目推荐
相关项目推荐