首页
/ Puppeteer Browser.version() 解析:获取浏览器名称与版本号的 API 用法与源码实现

Puppeteer Browser.version() 解析:获取浏览器名称与版本号的 API 用法与源码实现

2026-09-04 23:19:56作者:郁楠烈Hubert

Browser.version() 是 Puppeteer 中用于获取当前已连接浏览器的名称和版本号字符串的实例方法,返回形如 "Chrome/61.0.3153.0"Promise<string>。它在自动化测试中用于环境检测、浏览器兼容性判断、版本门控等功能,是理解 Puppeteer 底层 CDP / WebDriver BiDi 协议差异的关键入口。

overview

方法签名与返回值

Browser.version()api/Browser.ts 中作为抽象方法声明:

class Browser {
  abstract version(): Promise<string>;
}

方法无参数,返回一个 Promise<string>,resolve 值为浏览器的名称加版本号字符串。根据运行环境和浏览器类型,返回值的格式存在差异:

运行环境 返回值示例 说明
无头 Chrome(旧 headless) "HeadlessChrome/61.0.3153.0" 名称前缀为 HeadlessChrome
有头 Chrome 或新 headless 模式 "Chrome/61.0.3153.0" 名称前缀为 Chrome
Firefox "Firefox/116.0a1" 名称前缀为 Firefox

官方文档明确提示:返回值的格式可能随浏览器的未来版本发布而变化。因此,生产代码中不应将返回值与某个固定字符串做严格相等比较,而应通过解析前缀或版本号区间来判断特性。

CDP 协议的实现:调用 Browser.getVersion

Chrome/Chromium 浏览器通过 Chrome DevTools Protocol(CDP)通信。在 cdp/Browser.ts 中,version() 方法的具体实现如下:

// packages/puppeteer-core/src/cdp/Browser.ts
override async version(): Promise<string> {
  const version = await this.#getVersion();
  return version.product;
}

它调用了私有方法 #getVersion()第 713-725 行),该方法内部通过 Connection.send 发送 CDP 命令 Browser.getVersion

async #getVersion(): Promise<Protocol.Browser.GetVersionResponse> {
  if (!this.#version) {
    this.#version = Deferred.create<Protocol.Browser.GetVersionResponse>();
    try {
      this.#version.resolve(
        await this.#connection.send('Browser.getVersion'),
      );
    } catch (error) {
      this.#version.reject(error as Error);
    }
  }
  return await this.#version.valueOrThrow();
}

这段实现有两个关键设计点:

  1. 结果缓存#version 是一个 Deferred 实例,首次调用时发起 CDP 请求,之后所有调用都复用缓存的 Promise,避免重复向浏览器端发送 Browser.getVersion 命令。
  2. 错误传播:若 CDP 请求失败(例如连接已断开),Deferred.reject 会将错误传播给所有等待该 Promise 的调用方。

version() 方法返回的是 GetVersionResponse 中的 product 字段。在旧版 headless 模式下,该字段值为 "HeadlessChrome/xx.x.x.x";在新 headless(Chrome 112+ 默认)或有头模式下,则为 "Chrome/xx.x.x.x"。这与 userAgent() 方法(第 685-688 行)返回 userAgent 字段的逻辑形成对照——两者共用同一个 #getVersion() 缓存,但提取的字段不同。

WebDriver BiDi 协议的实现

对于通过 WebDriver BiDi 协议连接的浏览器(如 Firefox 的 BiDi 模式),bidi/Browser.ts 中的实现直接拼接内部字段:

// packages/puppeteer-core/src/bidi/Browser.ts
override async version(): Promise<string> {
  return `${this.#browserName}/${this.#browserVersion}`;
}

#browserName#browserVersion 在 BiDi 连接建立时,从 session.new 的 capabilities 响应中提取。这种实现不依赖额外的协议命令调用,而是在会话初始化阶段就已经拿到了版本信息,因此 version() 的调用几乎是零开销的。

实际用法示例

以下代码演示了如何启动浏览器并获取版本号:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const version = await browser.version();
console.log(version); // 例如: "Chrome/131.0.6778.204" 或 "HeadlessChrome/131.0.6778.204"

// 版本门控示例:判断是否为 Chrome 149+
const majorVersion = parseInt(version.match(/\d+/)?.[0] ?? '0', 10);
if (majorVersion < 149) {
  console.warn('某些功能需要 Chrome 149 或更高版本');
}

// 判断是否为 headless 模式
const isHeadless = version.startsWith('HeadlessChrome');
console.log(`当前运行模式: ${isHeadless ? '无头' : '有头'}`);

await browser.close();

上述版本门控模式在 Puppeteer 源码中本身就有应用。在 cdp/Browser.tscreate 静态方法中,当用户传入 allowlist 选项时,会主动调用 #getVersion() 解析主版本号并检查是否满足最低要求:

if (allowlist) {
  const version = await browser.#getVersion();
  const majorVersion = parseInt(
    version.product.match(/\d+/)?.[0] ?? '0',
    10,
  );
  if (majorVersion < 149) {
    throw new Error('The allowlist option require Chrome 149 or greater.');
  }
}

这说明 version() 的返回值在 Puppeteer 内部不仅用于用户侧检测,也是实现特性门控的基础设施。

userAgent() 方法的区别

方法 返回内容 示例(Chrome) 示例(Firefox)
browser.version() 名称/版本号 "Chrome/131.0.6778.204" "Firefox/134.0"
browser.userAgent() 完整 User-Agent 字符串 "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" "Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0"

version() 返回的是简洁的标识字符串,适合用于版本比较和程序逻辑判断;userAgent() 返回的是完整的 HTTP User-Agent,适合用于需要精确匹配 UA 的场景(如反爬检测模拟)。两者在 CDP 实现中共用同一个 Browser.getVersion 命令的结果缓存(第 680-688 行),不会产生额外的协议调用开销。

测试验证

Puppeteer 仓库中的 test/src/browser.test.ts 包含了针对 Browser.version() 的专项测试:

describe('Browser.version', function () {
  it('should return version', async () => {
    const {browser} = await getTestState();
    const version = await browser.version();
    expect(version.length).toBeGreaterThan(0);
    expect(version.toLowerCase()).atLeastOneToContain(['firefox', 'chrome']);
  });
});

该测试验证了两个核心不变量:

  1. 返回值非空;
  2. 返回值(不区分大小写)必须包含 firefoxchrome 字样。

这确保了无论底层是 Chrome(CDP 或 BiDi)还是 Firefox,version() 的返回值都遵循 名称/版本 的基本格式约定。

注意事项与最佳实践

  1. 不要在 browser.close() 之后调用:CDP 实现依赖活动连接,连接断开后调用将抛出错误。BiDi 实现不受此影响,因为它从会话初始化时就缓存了版本信息。
  2. 不要依赖固定格式:文档明确声明返回值格式可能随浏览器版本变化。建议通过正则提取数字部分做版本比较,而非字符串全等匹配。
  3. 区分 headless 与有头模式:旧版 headless Chrome 返回 HeadlessChrome/... 前缀,新 headless 和有头模式返回 Chrome/... 前缀。如果需要判断运行模式,应使用 version.startsWith('Headless') 而非 !version.startsWith('Chrome')
  4. 版本缓存的语义:CDP 实现中版本信息在首次 version() 调用时被缓存,之后不会更新。由于浏览器进程在运行期间版本号不会变化,这是合理的设计。

相关文档

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