首页
/ @puppeteer/browsers 的 createProfile 详解:为 Firefox 自动生成可用的浏览器 Profile

@puppeteer/browsers 的 createProfile 详解:为 Firefox 自动生成可用的浏览器 Profile

2026-09-07 10:16:48作者:幸俭卉

导读

createProfile()@puppeteer/browsers 包中用于"浏览器侧配置准备"的公开函数:它接收一个浏览器类型与一组偏好设置,在指定目录生成一个可供启动的浏览器 Profile。目前该函数只对 Firefox 生效——它会在目标目录写入一份 user.js 偏好文件(并安全备份已有文件),让 Firefox 以干净的、适合自动化测试的状态启动。读完本文你将掌握 createProfile 的签名与参数语义、它与其他 API(如 computeExecutablePathlaunch)的配合方式,以及其底层基于 user.js/prefs.js 的实现原理与默认偏好策略。

本文依据 docs/browsers-api/browsers.createprofile.md 及同目录 API 索引(docs/browsers-api/index.md)展开,并对照仓库源码核实实现细节。

函数定位与签名

createProfile 属于 @puppeteer/browsers 包通过 packages/browsers/src/main.ts 对外导出的公开 API 之一(与 installlaunchresolveBuildId 等并列),在 API 目录中与 launchcomputeExecutablePath 等并列索引。其 TypeScript 签名如下:

export declare function createProfile(
  browser: Browser,
  opts: ProfileOptions,
): Promise<void>;

要点:

  • 返回值是 Promise<void>,即该函数只负责"准备 Profile 目录",不启动浏览器、不返回句柄。
  • 参数 browser 决定按哪款浏览器的 Profile 格式生成内容。
  • 参数 opts 提供 Profile 的落盘路径与自定义偏好。

参数解析

browser: Browser——目标浏览器枚举

browser 来自 Browser 枚举,完整成员与字符串值为:

成员 说明
CHROME "chrome" Chrome / Chrome for Testing
CHROMEDRIVER "chromedriver" ChromeDriver
CHROMEHEADLESSSHELL "chrome-headless-shell" Chrome 无头专用 shell
CHROMIUM "chromium" Chromium
FIREFOX "firefox" Firefox

实际分发逻辑位于 packages/browsers/src/browser-data/browser-data.ts

export async function createProfile(
  browser: Browser,
  opts: ProfileOptions,
): Promise<void> {
  switch (browser) {
    case Browser.FIREFOX:
      return await firefox.createProfile(opts);
    case Browser.CHROME:
    case Browser.CHROMIUM:
      throw new Error(`Profile creation is not support for ${browser} yet`);
  }
}

由此可以得到明确的能力边界

  • FIREFOX 是当前唯一完整支持的浏览器,调用会真正写入 Profile 文件;
  • CHROMECHROMIUM 会抛出 Profile creation is not support for <name> yet 错误,表明 Chrome/Chromium 的 Profile 生成尚未实现;
  • 从源码结构看,CHROMEDRIVERCHROMEHEADLESSSHELL 在该 switch 中没有对应分支,会静默落入空分支,不执行任何文件操作,也不会报错。因此在实际使用中请只在目标为 Firefox 时调用本函数。

opts: ProfileOptions——Profile 配置

完整定义见 ProfileOptions 接口文档,其类型声明位于 packages/browsers/src/browser-data/types.ts

export interface ProfileOptions {
  preferences: Record<string, unknown>;
  path: string;
}
属性 类型 是否必填 含义
path string Profile 目录的绝对/相对路径。若不存在会被递归创建(见下文实现);若已存在,其中的 user.jsprefs.js 会被安全备份后写入/保留
preferences Record<string, unknown> 以"Firefox 偏好键 → 值"形式表示的偏好字典,值可以是字符串、数字或布尔值,最终序列化为 user.js 中的 user_pref(key, value) 条目

底层实现原理:user.js 与 prefs.js

Firefox 与 Chrome 的 Profile 机制不同:Chrome 主要依赖 Local State/Preferences(JSON 文件)等,而 Firefox 的自动化配置习惯通过 user.js 注入——Firefox 在启动时会读取 user.js 并把其中的条目自动同步进自身的 prefs.js 配置文件。这正是 createProfile 的落点。

Firefox 分支实现位于 packages/browsers/src/browser-data/firefox.ts

export async function createProfile(options: ProfileOptions): Promise<void> {
  if (!fs.existsSync(options.path)) {
    await fs.promises.mkdir(options.path, {
      recursive: true,
    });
  }
  await syncPreferences({
    preferences: {
      ...defaultProfilePreferences(options.preferences),
      ...options.preferences,
    },
    path: options.path,
  });
}

整个流程分三步:

  1. 目录准备:若 opts.path 不存在,则以 recursive: true 递归创建,因此直接传入一个尚不存在的目录(如临时目录下的子目录)即可,无需手工 mkdir
  2. 偏好合并:将一组"面向自动化测试的默认偏好"与用户传入的 preferences 合并,用户自定义条目永远覆盖默认值(源码中默认值在前、options.preferences 在后展开,后者的同名键胜出;defaultProfilePreferences 内部同样通过 Object.assign(defaultPrefs, extraPrefs) 保证额外条目生效)。
  3. 写入偏好文件:调用内部函数 syncPreferences 将合并结果写为 user.js

syncPreferences:备份与原子写入

syncPreferences 位于 packages/browsers/src/browser-data/firefox.ts

async function syncPreferences(options: ProfileOptions): Promise<void> {
  const prefsPath = path.join(options.path, 'prefs.js');
  const userPath = path.join(options.path, 'user.js');

  const lines = Object.entries(options.preferences).map(([key, value]) => {
    return `user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`;
  });

  // Use allSettled to prevent corruption.
  const result = await Promise.allSettled([
    backupFile(userPath).then(async () => {
      await fs.promises.writeFile(userPath, lines.join('\n'));
    }),
    backupFile(prefsPath),
  ]);
  for (const command of result) {
    if (command.status === 'rejected') {
      throw command.reason;
    }
  }
}

值得注意的实现细节:

  • 写入格式:每一对偏好被序列化成标准 Firefox 语法 user_pref("preference.key", value);(键与值均经 JSON.stringify 转义),多个条目以换行连接后整体写入 user.js
  • 自动备份backupFilefirefox.ts)会检测文件是否存在,若存在则把它复制为 user.js.puppeteerprefs.js.puppeteer。也就是说,在自动化接管一个已有的 Firefox Profile 之前,原配置会保留一份带 .puppeteer 后缀的副本,便于排查问题或恢复原状。
  • 防损坏设计:写入与备份通过 Promise.allSettled 并行执行,任何一个子任务失败都会被收集并在末尾统一抛出,避免半途失败导致 Profile 文件损坏。

默认偏好集:为自动化测试准备的"干净" Firefox

createProfile 最大的价值之一,是它内置了一套经过大量实践沉淀的默认偏好(定义于 defaultProfilePreferencesfirefox.ts)。即使你传入空的 preferences: {},生成的 Profile 也已经是一个"离线、无打扰、无更新弹窗、行为可预期"的测试环境。归纳起来可分成几类:

禁用更新与网络请求

  • app.update.disabledForTesting: trueapp.update.checkInstallTime: false
  • browser.safebrowsing.* 系列全部关闭(blockedURIs/downloads/malware/phishing)
  • browser.search.update: falseextensions.update.enabled: false
  • services.settings.server: 'data:,#remote-settings-dummy/v1'(远端设置指向本地空源)
  • network.sntp.poolsextensions.webservice.discoverURLdatareporting.healthreport.* 指向内置假地址 dummy.test,确保完全离线

关闭启动打扰

  • browser.startup.page: 0browser.startup.homepage: 'about:blank'
  • browser.startup.homepage_override.mstone: 'ignore'browser.newtabpage.enabled: false
  • browser.usedOnWindows10.introURL: ''startup.homepage_welcome_url: 'about:blank'
  • browser.shell.checkDefaultBrowser: false(不检查默认浏览器)

测试稳定性相关

  • browser.tabs.warnOnCloseOtherTabs/warnOnOpen: falsebrowser.warnOnQuit: false(关闭各种警告对话框)
  • browser.sessionstore.resume_from_crash: false(崩溃后不恢复标签)
  • focusmanager.testmode: true(后台运行也可获得焦点)
  • toolkit.cosmeticAnimations.enabled: false(关闭动画,避免截图/点击时序抖动)
  • dom.disable_open_during_load: false(放开自动弹窗限制,配合弹窗类测试)
  • dom.file.createInChild: true——为 CDP 的 Page.setFileInputFiles 提供 File 对象支持
  • remote.bidi.dismiss_file_pickers.enabled: true——配合 WebDriver BiDi 自动关闭文件选择器(源码注释指出这是为了规避 Bug 1999693)
  • signon.autofillForms: falsesignon.rememberSignons: falsesecurity.notification_enable_delay: 0

调试友好

  • browser.dom.window.dump.enabled: true(把 dump() 输出送到系统控制台)
  • javascript.options.showInConsole: true(把 JS 错误显示到控制台,便于断言错误日志为空)
  • 关闭 devtools.jsonviewscreenshots 组件等噪音来源

合并策略很简单:默认值是底座,你的 preferences 是同键覆盖的增量。因此你只需关心自己需要改动的那几项,例如显式设置 'browser.startup.page': 1 让启动即打开指定页面。

实战用法:与 launch / computeExecutablePath 配合

createProfile 的典型使用场景是:在临时目录准备 Profile → 解析可执行文件路径 → 用 --profile <dir> 启动 Firefox。仓库中的官方测试 packages/browsers/test/src/firefox/launch.test.ts 给出了完整范式:

import {createProfile} from '@puppeteer/browsers';
// ...

await createProfile(Browser.FIREFOX, {
  path: userDataDir,        // 例如某临时目录下的 'profile' 子目录
  preferences: {},          // 使用默认偏好集即可
});

const executablePath = computeExecutablePath({
  cacheDir: tmpDir,
  browser: Browser.FIREFOX,
  buildId: testFirefoxBuildId,
});

const process = launch({
  executablePath,
  args: [
    '--foreground',   // macOS 需要
    // '--wait-for-browser', // Windows 需要
    '--profile', userDataDir,
    '--headless',
    'about:blank',
  ],
});
await process.close();

关键点归纳:

  1. 先调用 createProfile 生成 Profile 目录,再把它通过命令行参数 --profile <dir> 传给 Firefox;
  2. computeExecutablePath(参见 docs/browsers-api/browsers.computesystemexecutablepath.md 同族 API)用于从缓存目录解析出二进制路径;
  3. launch 本身只负责拉起进程(详见 launch 文档),不负责 Profile 准备,所以两者需要显式组合使用

由于 createProfile 幂等地写入并备份 user.js,同一测试多次运行、甚至复用同一个 Profile 目录都不会破坏文件完整性;.puppeteer 后缀备份则是你在排查"某次启动后浏览器表现异常"时的第一手还原点。

此外,从源码结构可以推断,createProfile/ProfileOptions 也被更上层的 Puppeteer 主库所引用——在 packages/puppeteer-core/src/node/FirefoxLauncher.ts 中即出现了对 createProfile/ProfileOptions 相关能力的引用,说明当用户通过 puppeteer-core 直接操作 Firefox 时,底层启动器同样依赖本函数来准备用户数据目录。

调用示例(完整可运行片段)

把上述流程浓缩成一个最小可运行片段(在已安装 @puppeteer/browsers 的项目中):

import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {
  Browser,
  BrowserPlatform,
  createProfile,
  install,
  launch,
  computeExecutablePath,
  detectBrowserPlatform,
} from '@puppeteer/browsers';

const cacheDir = join(tmpdir(), 'puppeteer-browsers-example');
const profileDir = join(tmpdir(), 'firefox-profile-example');

await install({
  browser: Browser.FIREFOX,
  buildId: 'latest', // 或者显式 buildId
  platform: detectBrowserPlatform() ?? BrowserPlatform.LINUX,
  cacheDir,
});

await createProfile(Browser.FIREFOX, {
  path: profileDir,
  preferences: {
    // 例:在默认"干净配置"基础上追加自定义项
    'browser.startup.page': 1,
    'browser.startup.homepage': 'https://example.com',
    'signon.autofillForms': false,
  },
});

const executablePath = computeExecutablePath({
  cacheDir,
  browser: Browser.FIREFOX,
  buildId: 'latest',
});

const proc = launch({
  executablePath,
  args: ['--profile', profileDir, '--headless', 'about:blank'],
});
await proc.close();

运行后检查 profileDir 目录,应能看到生成的 user.js,其内容形如:

user_pref("browser.startup.page", 1);
user_pref("browser.startup.homepage", "https://example.com");
...

注意事项与边界

  • 浏览器支持有限:正如文档与源码所确认,createProfile 目前仅服务 Firefox;针对 Chrome/Chromium 会直接抛错,请勿在非 Firefox 场景调用并期待返回值。
  • 不会启动进程:函数只保证 Profile 目录就绪,真正的进程生命周期由 launch 管理。
  • 备份后缀约定:重复调用时,已存在的 user.js/prefs.js 会被复制为同名 .puppeteer 文件,随后才写入/保留新内容;如需完全重置,可自行清空目标 Profile 目录后再调用。
  • preferences 值为未知类型时的写入:序列化依赖 JSON.stringify,因此传入 undefined 等非 JSON 可表示值时,对应 user_pref 条目的值可能不符合 Firefox 预期,建议只传 string | number | boolean
  • 版本前提:本文以当前仓库中的 @puppeteer/browsers 源码为准,若你使用 npm 上其它版本的包,请以该版本实际的导出与行为为准。

小结

createProfile 把"为 Firefox 准备自动化 Profile"这件事封装成了一个签名简单、行为幂等的公开 API:目录不存在自动创建、默认偏好自动注入、用户偏好同键覆盖、旧配置安全备份。配合 installcomputeExecutablePathlaunch,它构成了 @puppeteer/browsers 从"下载浏览器"到"以干净配置拉起 Firefox"的完整闭环,也让上层 puppeteer-core 的 Firefox 启动器能够复用同一套经过验证的 Profile 生成逻辑。

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