Playwright 中的自动化信号与一致性
了解 Playwright 和 Puppeteer 会向页面暴露哪些自动化信号,为什么页面加载后的 JavaScript 补丁并不稳固,以及如何自行验证 BotBrowser 的配置。
BotBrowser Team
Playwright 和 Puppeteer 会话可能向页面展示若干与自动化相关的信号:navigator.webdriver 标志、页面上下文中的框架绑定、Chrome DevTools Protocol(CDP)的副作用,以及无头与有界面运行之间的差异。加载配置文件后,BotBrowser 会控制 navigator.webdriver,并且在 ENT Tier1 上让受保护的控制台和运行时 CDP 事件不改变页面可观察到的行为。框架绑定、视口设置,以及代理、配置文件和流量行为的一致性,仍然由你自己的测试代码负责。下面依次说明每一类信号,解释为什么页面加载后再打补丁比在浏览器本身控制更脆弱,并介绍如何在你自己的测试环境中验证结果。
页面能看到哪些自动化信号
自动化框架需要在浏览器中设置挂钩才能工作。这些挂钩位于渲染页面的同一个浏览器里,所以其中一部分可以被页面中运行的 JavaScript 读取。实际中有四类信号值得关注,想要一致的测试环境时,每一类的负责方各不相同。
navigator.webdriver 标志
W3C WebDriver 规范把 navigator.webdriver 定义为页面得知浏览器处于自动化控制之下的一种方式。它被设计为透明机制,因此标准 Chromium 的自动化会话通常返回 true。它是最容易读取的信号之一,一个表达式就能得到结果。这也是测试环境行为异常时最先应该检查的值。
框架绑定
Playwright 会向页面上下文添加 __playwright__binding__ 和 __pwInitScripts 这样的辅助名称,以便框架与页面通信。这些名称的存在源于框架与浏览器通信的方式,是设计要求,不是缺陷。Puppeteer 不会注入同样的绑定,所以它主要的一致性问题不同:如果它的默认视口覆盖仍然生效,窗口尺寸就不再与配置文件匹配。
CDP 副作用
Playwright 和 Puppeteer 都通过 CDP 驱动 Chromium。某些运行时一致性检查可以从自动化可能引起的控制台或异常行为变化中推断出 CDP 连接,例如客户端启用 Console 或 Runtime 域的时候。这类信号关乎行为而不是可见属性,所以属性检查结果正常并不能排除它。
后文介绍的保护机制会带来一个实际后果:控制台抑制生效时,你的自动化客户端不会收到页面转发的控制台消息。在自己的脚本中依赖控制台监听器的团队,应在首次生产运行前考虑这一点。
无头与有界面的差异
无头会话和有界面会话在屏幕几何、插件列表和图形行为上可能不同。配置文件会向两种模式提供相同的预期值,但主机的显示服务和图形后端仍然会影响页面测得的结果。请在自己的环境中比较你实际使用的模式,而不是假定它们一致。关于无头与有界面配置文件一致性的指南介绍了相应的评审方法。
为什么页面加载后再打补丁并不稳固
常见做法是使用社区插件,在页面内运行并于浏览器启动后修改取值。它用 JavaScript 的 getter 替换 navigator.webdriver,从全局作用域删除框架名称,并调整其他属性。这种做法有一些对一致性很重要的局限。
第一,它与页面处在同一个 JavaScript 世界。脚本运行之前浏览器报告的值会被脚本定义的值替换,而替换后的值在形态上可能不同于浏览器自身的实现。浏览器从未定义过的属性,与被脚本删除的属性,在后续代码看来并不总是一样。
第二,每个新页面、框架和上下文都要重新应用,补丁也未必能覆盖每个执行上下文。每增加一层,就多一项要在框架或浏览器升级后保持同步的内容。补丁列表变长时,风险不只是某个补丁失效,多个补丁叠加还可能让会话不再像任何真实浏览器。
第三,修改一个属性的补丁无法解决其他属性。例如更改 User-Agent 字符串,并不会影响 navigator.webdriver、框架绑定或 CDP 行为,还可能让你呈现的浏览器身份与实际运行的环境不一致。
有些团队选择自行编译去掉自动化开关的 Chromium。这样可以从源头消除一类信号,但也意味着要维护一个分支:每个 Chrome 版本都要变基、解决冲突,并承担漫长的构建。对多数团队来说,这样的投入很难与一个维护中、且记录了自身控制范围的浏览器相比。
仍有一种范围很窄的后置补丁是合理的。针对 Playwright 两个名称的 addInitScript 清理会在任何页面脚本之前运行,并且只移除框架添加的内容。它是一个小而有记录的步骤,不是庞大的覆盖层,BotBrowser 文档也把它保留在推荐配置中。
BotBrowser 为自动化一致性记录了什么
BotBrowser 为自动化场景记录了下列控制项。每一行给出已记录的行为以及适用的层级,方便你决定哪些应该放进自己的配置。
| 控制项 | 已记录的行为 | 层级 |
|---|---|---|
已加载的配置文件(--bot-profile) | 自动控制 navigator.webdriver,无需额外标志 | Core |
--bot-disable-console-message | 在 CDP 连接期间,让受保护的控制台和运行时事件不改变页面可见的行为;默认开启 | ENT Tier1 |
--bot-disable-debugger | 忽略 JavaScript 的 debugger 语句,使运行不会暂停 | Core |
--bot-always-active | 窗口和标签页失去焦点时仍保持活动状态;默认开启 | PRO |
--bot-port-protection | 防止远程页面探测 localhost 端口上运行着哪些服务 | PRO |
--bot-script | 在具有特权的隔离页面上下文中运行你的脚本,没有外部框架绑定,也没有单独的 CDP 客户端 | Core |
控制台标志需要准确理解。它保护的是控制台和运行时事件路径,并不会禁用 CDP Runtime 域,因此你的自动化仍可正常执行求值和处理异常。若用 --bot-disable-console-message=false 有意关闭保护,会改变 CDP 行为,所以请把这类诊断会话当作单独的兼容性运行,不要拿它的结果与生产运行比较。
--bot-script 的框架占用最小,因为它既不需要 Playwright,也不需要 Puppeteer。如果你的流程可以写成在浏览器内运行的脚本,就能完全避开框架绑定。页面标题显示扩展名称时,请使用 --bot-title。
配置 Playwright 和 Puppeteer
请安装 playwright-core 或 puppeteer-core,而不是完整的软件包。完整软件包会自带 Chromium 下载,而你按路径启动 BotBrowser 时并不需要它。请把配置文件、可执行文件路径和代理放在环境变量或配置中,让每次运行记录的输入保持一致。
import { chromium } from 'playwright-core';
const browser = await chromium.launch({
executablePath: process.env.BOTBROWSER_EXEC_PATH,
headless: true,
args: [
'--disable-audio-output',
`--bot-profile=${process.env.BOT_PROFILE_PATH}`,
'--proxy-server=socks5://user:pass@proxy.example.com:1080',
],
});
const page = await browser.newPage();
await page.addInitScript(() => {
delete window.__playwright__binding__;
delete window.__pwInitScripts;
});
await page.goto('https://example.com');
先创建页面,注册清理脚本,然后再导航。初始化脚本必须在第一次 goto 之前就位,否则页面可能在名称被删除之前就已运行。同样不要在 Playwright 中设置视口选项,因为尺寸应由配置文件控制。
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.launch({
executablePath: process.env.BOTBROWSER_EXEC_PATH,
headless: true,
defaultViewport: null,
args: [
'--disable-audio-output',
`--bot-profile=${process.env.BOT_PROFILE_PATH}`,
'--proxy-server=socks5://user:pass@proxy.example.com:1080',
],
});
const page = await browser.newPage();
await page.goto('https://example.com');
在 Puppeteer 中,defaultViewport: null 是关键的一行。缺少它时,Puppeteer 会应用自己的视口,覆盖配置文件提供的屏幕尺寸,而视口不匹配就是你自己造成的一致性问题。
请通过 --proxy-server(或按上下文设置的代理选项)配置代理,而不要使用仅限框架的代理设置。只有由 BotBrowser 自己转发流量时,它才会让时区、区域设置和语言与代理所在地区对齐。手动设置的 --bot-timezone、--bot-locale 或 --bot-languages 会替换这种映射,而保持为 auto 的值会继续跟随代理。如果你用 --bot-profile-dir 从目录加载配置文件,BotBrowser 会在每次启动时选择其中一个,并且目录选项不能与 --bot-profile 同时使用(如果两者都指定,以目录为准)。
在 Linux 服务器上,即使使用 --headless 启动,BotBrowser 仍然需要 Xvfb 之类的虚拟显示,并且每次运行 BotBrowser 都必须设置 DISPLAY 变量。对长时间运行的任务,有两个 PRO 标志值得了解。--bot-always-active 默认开启,会让失去焦点的窗口和标签页保持活动状态,这与主力浏览器的行为一致。--bot-port-protection 会防止远程页面得知本地端口上运行着哪些服务,例如远程桌面或开发服务器。这两个标志都不会改变 Playwright 或 Puppeteer 的连接方式,所以可以在基础检查通过后再加入启动参数。
在自己的测试环境中验证配置
验证是对你自己配置的回归检查。它能告诉你当前的启动是否符合已记录的行为,以及在更新 BotBrowser、配置文件或框架之后是否仍然符合。它不会预测任何第三方网站如何对待一个会话。
先记录输入:BotBrowser 版本、配置文件、启动参数、框架版本、无头或有界面模式,以及代理所在地区。没有这份记录,之后出现差异时就无法归因到具体变更。
然后在你自己控制的页面上读取一小组取值,并与配置文件和启动设置应当产生的结果比较:
- 加载配置文件后,
navigator.webdriver应为false。 - 如果初始化脚本在导航前运行过,Playwright 的两个绑定名称应当不存在。
- 窗口和屏幕尺寸应与配置文件一致,语言和插件条目应与预期的区域设置一致。
- 控制台行为应符合你的意图:在 ENT Tier1 默认设置下,客户端的控制台监听器不应收到页面的任何消息。
- 时区和语言应跟随代理所在地区,或你明确设置的值。
当某个取值不同时,文档为每种情况指出了具体原因:
| 现象 | 可能原因 | 需要调整 |
|---|---|---|
navigator.webdriver 返回 true | 配置文件没有加载 | 检查 --bot-profile 的路径和文件 |
| Playwright 绑定名称仍然存在 | 初始化脚本运行得太晚 | 在第一次 goto 之前注册 addInitScript |
| 控制台事件仍然到达客户端 | 抑制已关闭,或层级不包含该功能 | 检查标志取值和订阅层级 |
| 视口与配置文件不一致 | 框架的视口覆盖仍然生效 | 在 Puppeteer 中使用 defaultViewport: null,在 Playwright 中避免视口选项 |
| 时区与代理所在地区不一致 | 代理只通过框架选项设置 | 用 --proxy-server 或按上下文的代理来传入代理 |
你自己控制的页面可以作为同一会话的第二意见。请把它的输出当作你自己环境的信息,而不是第三方网站作出的结论。即使从来没有网站提出异议,指向配置文件、代理和区域设置之间不一致的发现也值得修正。
在流水线中,请把这份清单变成断言,当预期值发生变化时让构建失败。请在生产任务所用的同一模式下运行。如果你同时运行无头和有界面任务,两者都要运行。把版本记录与结果放在一起,这样升级后出现的失败就容易追溯到发生变化的组件。
一次失败运行的复盘
假设某个夜间任务开始报告,测试页面上的语言列表与配置文件的区域设置不再一致。请从记录入手,而不是从标志入手。把 BotBrowser 版本、配置文件、启动参数和代理所在地区与上一次成功运行对比。在这个例子里,只有代理所在地区变了,因为团队把任务迁移到了另一个出口位置。
下一步文档已有说明。保持为 auto 的值会跟随代理,所以语言列表随地区一起改变了。如果团队希望无论出口位置如何都使用固定的区域设置,就显式设置 --bot-locale 或 --bot-languages,并让时区保持为 auto。修改之后,再次运行同样的五项检查并更新记录。最终得到的是一项有记录的决定:哪些值跟随代理,哪些被固定,而不是日后才发现的意外。
保持代理、配置文件和流量一致
自动化信号只是一个连贯会话的一部分。把 Windows 版 Chrome 配置文件搭配某个国家的代理和另一个国家的区域设置,会产生不一致,除非你固定区域设置或更换线路。请决定哪些属性跟随线路,哪些保持固定,把这个决定写下来,并用检查 navigator.webdriver 的同一套流程来测试。
流量行为也属于同一次评审。如果脚本以真人不会采用的节奏打开大量页面,或者以固定的节拍重复相同的导航,这就是任何浏览器设置都无法改变的行为模式。BotBrowser 不能替代这种自律。请让请求量、节奏和账号使用保持在你所合作网站的规则之内,并且只运行你有权运行的自动化。
最后,当工作需要相互独立的环境时,请使用多个配置文件。所有实例复用同一个配置文件,意味着每个实例报告的都是同一个环境。--bot-profile-dir 会在每次启动时从目录中选取一个文件,无需额外代码就能让每次启动使用不同的配置文件,并且你仍然可以记录某次运行加载了哪个文件。
边界、Selenium 与常见实践问题
在你自己的 Playwright 或 Puppeteer 测试页面中,BotBrowser 会在加载配置文件后自动控制 navigator.webdriver,并且通过 --bot-disable-console-message(ENT Tier1,默认开启)让受保护的控制台和运行时 CDP 事件不改变页面可观察到的行为,因此你可以用上文的检查自行核对这些自动化信号。已记录的行为见自动化一致性文档。边界同样明确:BotBrowser 不能保证第三方站点如何判定一个会话,不会禁用 CDP Runtime 域,不会替你移除框架绑定(请保留 addInitScript 清理),也不能替代一致的代理、配置文件和流量行为。
BotBrowser 支持 Selenium 吗? 已记录的配置使用 Playwright 和 Puppeteer。Selenium 通过 WebDriver 协议通信,可能暴露这里所述 CDP 保护之外的额外信号。加载配置文件后,BotBrowser 仍会控制 navigator.webdriver,但控制台和运行时保护仅限于 CDP 路径。
还需要再加一个补丁插件吗? 文档没有把这类插件列入推荐配置。覆盖相同属性的插件会增加第二个取值来源,所以应先在没有它的情况下测试。只有当确实存在经过验证的具体缺口时才添加。
--bot-disable-debugger 会影响我的调试吗? 会。页面 JavaScript 中的 debugger 语句会被忽略,运行不会在这些语句处停下。需要这种暂停的会话不要使用该标志,无人值守的运行可以使用。
生产环境需要控制台输出怎么办? 请在应用层记录,写入文件或外部服务,而不是依赖 CDP 的控制台转发。短时间的调试可以在单独的会话中设置 --bot-disable-console-message=false。
如何在更新之间保持结果稳定? 把 BotBrowser 可执行文件、配置文件和框架版本放在同一份发布记录中。其中任何一项变化后,都要重新执行上文的检查。每次更新时请查阅你所用标志的文档,因为默认值和层级可能随版本变化。
关于配置指南,请参阅 Playwright 入门和 Puppeteer 入门。关于配置文件的组织,请参阅配置文件管理。