headless 和 headed 结果不一样:Playwright 差异在哪几处

2026-08-18

一份用例在本地加上 --headed 看着一切正常,推到 CI 上就开始飘;或者反过来,CI 一直绿的用例,同事在本机开着窗口跑就是点不中。这时候最省时间的做法不是继续加 waitForTimeout,而是先搞清楚:这两种形态到底哪几处真的不一样。

Playwright 仓库里能查到的差异比想象中少,而且都很具体。下面按「差异来自哪里」排,每一处都标出仓库里的出处。本文对照的是同一个项目的两种运行形态,不排优劣。

第一处:你跑的可能根本不是同一个可执行文件

这一处最容易被忽略。docs/src/browsers.md 的「Chromium: headless shell」一节写明,Playwright 为 headed 操作提供常规 Chromium 构建,为 headless 模式单独提供一个 chromium headless shell。

源码那一侧对得上。packages/playwright-core/src/server/chromium/chromium.ts 里的 getExecutableName() 逻辑是:如果 channel 是 Chromium 别名,返回 chromium;如果指定了其它 channel,返回该 channel;都没有的时候,才按 options.headlesschromium-headless-shellchromium 之间二选一。

把这两处放在一起,能读出一个对排查很有用的结论:只要你在配置里写了 channel,headless 与 headed 用的就是同一个可执行文件;只有在没写 channel 的默认情况下,两种形态才会落到两个不同的二进制上。docs/src/browsers.md 里那句「如果只在 headless shell 下跑测试(即没有指定 channel 选项)」也正是这个前提。

同一节还给出了安装期的两个开关,命令原样抄自仓库文档:

npx playwright install --with-deps --only-shell
npx playwright install --with-deps --no-shell

前者只装 headless shell、不下载完整 Chromium;后者配合 channel: 'chromium' 使用,跳过 headless shell 的下载。docs/src/browsers.mdchannel: 'chromium' 描述为「选进新的 headless 模式」,docs/src/api/params.mdchannel 的说明也是同一句。

还有一条警告值得单独抄出来:同一份文档在「Google Chrome & Microsoft Edge」一节写明,Google Chrome 与 Microsoft Edge 已经切换到更接近常规 headed 模式的新 headless 实现,这与 Playwright 默认 headless 时使用的 chromium headless shell 不同,某些情况下要预期不同的行为,并指向了仓库的 issue #33566。也就是说,「headless 与 headed 有差异」这句话本身,在指定了 chrome / msedge 之后含义就变了。

第二处:启动参数只差几个,但差得很关键

packages/playwright-core/src/server/chromium/chromiumSwitches.ts 导出的那一大串开关,两种形态是共用的——--disable-back-forward-cache--force-color-profile=srgb--disable-extensions 这些都在里面,跟 headless 与否无关。

真正按形态分叉的只有 chromium.ts_innerDefaultArgs() 里那一段:当 options.headless 为真时,额外追加 --headless,再追加 --hide-scrollbars--mute-audio,以及一个 --blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4。headed 分支没有这几个。这几个开关本身属于 Chromium 的实现,仓库里只写了开关名,没有展开解释它们对页面的具体影响——所以这里只能说到「有没有传」这一层。

Firefox 与 WebKit 的分叉更简单。packages/playwright-core/src/server/firefox/firefox.tsdefaultArgs() 里,headless 追加 -headless,headed 那一支追加的是 -wait-for-browser-foregroundpackages/playwright-core/src/server/webkit/webkit.ts 只在 headless 时追加 --headless,此外还有一条与形态无关但对本站读者有用的:在 process.platform === 'win32' 且不是 WSL 时,它会额外追加 --disable-accelerated-compositing。Windows 上跑 WebKit 与在 Linux 上跑,起手的开关就不一样,别把这笔账算到 headless 头上。

如果你想自己加开关,docs/src/api/params.mdargs 的说明带着一条警告:自定义浏览器参数风险自负,其中一些可能破坏 Playwright 的功能。ignoreDefaultArgs 的说明里更直白地写了「危险选项,谨慎使用」。

第三处:视口和设备像素比,其实跟 headless 无关

这是背锅最多的一处。很多人以为「headed 有真实窗口所以尺寸不同」,但按 docs/src/api/params.mdviewport 的说明,Playwright 为每个 page 模拟一致的视口,默认是 1280x720;deviceScaleFactor 的默认值是 1。这两个是仓库当前文档里的默认值,随版本可能变动。默认情况下,窗口大不大跟页面看到的视口没关系

真正会让视口依赖宿主窗口的是显式关掉它。同一份文档写得很清楚:JS 与 Java 传 null、C# 传 ViewportSize.NoViewport、Python 用 no_viewport,都是退出预设,让视口取决于操作系统定义的宿主窗口尺寸,并且原文直接标注了这会让测试的执行变得不确定。Python 侧 noViewport 的说明还多一句:不强制固定视口,允许在 headed 模式下调整窗口大小。

所以判断路径是:如果两种形态下页面布局不一致,先去看有没有人把视口关掉了,而不是先怀疑 headless。要改视口就按 docs/src/emulation.md 的写法显式设,注意它特意提醒过——viewport 属性必须写在展开 devices 之后,因为设备描述里本身也带 viewport(同一份文档对 isMobile 也有同样的顺序提醒)。另外 docs/src/api/params.mdisMobile 的说明写明它在 Firefox 上不被支持,这一条与 headless 与否无关,但换形态排查时常被顺手怀疑。

第四处:字体与渲染依赖

docs/src/test-snapshots-js.md 在讲截图基线命名时写明,快照文件名里带 chromium-darwin 这样的浏览器名加平台名,因为截图会因渲染、字体等因素在不同浏览器与平台间存在差异,所以需要各自的基线。注意这句话的分界是「浏览器与平台」,不是 headless 与 headed。

字体从哪来,也能在仓库里查到。packages/playwright-core/src/server/registry/nativeDeps.ts 的 Linux 依赖清单里列着 libfontconfig1fonts-liberationfonts-wqy-zenheifonts-noto-color-emojifonts-ipafont-gothic 这些包——它们是 install --with-deps 要装的系统依赖。中文字体包出现在这份清单里,本身就说明这些字体是宿主系统提供的、不随浏览器二进制走。至于装没装会渲染成什么样,我们没有跑过,不做描述。

什么时候必须用 headed

这几条是仓库里白纸黑字写了「需要 headed」的,不是经验之谈:

  • page.pause()docs/src/api/class-page.md 里这个方法的注记写明:该方法要求 Playwright 以 headed 模式启动,headless 选项为假值。
  • 带自定义 setup 的 codegendocs/src/codegen.md 的「Record using custom setup」示例里,四种语言的代码注释都是同一句「Make sure to run headed.」,随后是 chromium.launch({ headless: false }) 这样的写法。
  • 调试模式docs/src/debug.md 写明,JS 侧用 --debug 标志、其它语言绑定设 PWDEBUG 环境变量时,会额外配置两项默认值:浏览器以 headed 模式启动,默认超时设为 0(即不超时)。这两项是被动生效的——你不是选了 headed,是调试模式替你选了。
  • PWDEBUG=console。同一篇写明这时开发者工具控制台里会有一个 playwright 对象,可以调用 playwright.$(selector)playwright.locator(selector)playwright.selector($0) 这些方法。要用控制台,自然得有窗口。
  • Chrome 扩展docs/src/chrome-extensions-js-python.md 的注记写明扩展只在 Chromium 且使用持久化上下文时可用,并说明使用 chromium channel 才允许在 headless 模式下运行扩展,否则就得用 headed 模式启动。

反过来,docs/src/api/class-page.mdpage.pdf() 的说明并没有提 headless 与 headed 的限制,仓库里我们也没有找到「PDF 只能在某一种形态下生成」的当前说明——这一点不比。同样地,两种形态各自的运行速度、资源占用差多少,仓库里没有依据,本文不做任何描述。

Windows 与 Linux 的分叉

Windows 侧没有额外的显示服务要求,切换形态就是环境变量或命令行参数的事。以下命令与环境变量写法均原样抄自仓库文档:Python 侧是 pytest --browser webkit --headeddocs/src/test-runners-python.md 的 CLI 参数表里 --headed 的说明就是「以 headed 模式运行测试,默认 headless」);C# 侧 docs/src/running-tests-csharp.md 给的是 HEADED=1 dotnet test,并分别给出了 CMD 的 set HEADED=1 与 PowerShell 的 $env:HEADED="1" 两种写法。调试环境变量同理,仓库为 PWDEBUG 也分了 set PWDEBUG=1$env:PWDEBUG=1 两个 Windows 版本的标签页。

Linux 侧多一道坎。docs/src/ci.md 的「Running headed」一节写明:在 Linux 代理机上,headed 执行需要安装 Xvfb;官方 Docker 镜像与 GitHub Action 里已预装 Xvfb;要以 headed 模式跑,在实际命令前加 xvfb-run,例如 xvfb-run npx playwright test。这也解释了为什么「CI 上加 --headed」这件事在 Windows 自建机和 Linux 容器上是两种难度。

API 侧切换形态的写法,docs/src/debug.md 给的是把 headless: false 作为 launch 选项,并可以配合 slowMo

// Chromium, Firefox, or WebKit
await chromium.launch({ headless: false, slowMo: 100 });
# Chromium, Firefox, or WebKit
chromium.launch(headless=False, slow_mo=100)

以上为按仓库代码中的接口语义组合的示例,未经实测,以仓库最新代码为准。JS 的 Test Runner 侧还可以在配置的 use 里写 headlessdocs/src/test-use-options-js.md 的选项表里对它的描述是「是否以 headless 模式运行浏览器,默认 true」——同样是仓库当前文档里的默认值。

收口

按上面四处倒推,遇到「两种形态结果不同」时的顺序是:先看有没有指定 channel(决定是不是同一个二进制),再看视口有没有被关掉(决定布局是不是依赖宿主窗口),再看是不是截图基线跨平台跨浏览器的问题,最后才轮到那几个只在 headless 追加的启动开关。至于必须用 headed 的场景,仓库已经点名了 page.pause()、自定义 setup 的 codegen、调试模式与扩展这几类,照着办即可。


本文依据 github.com/microsoft/playwright 仓库于 2026-08-18 的公开内容整理, 事实来自仓库内的文档与源码。我们没有对文中涉及的功能做过实测, 因此不涉及运行速度、稳定性与实际表现的任何描述。 该项目迭代频繁,文中涉及的 API 签名、配置项与默认值随版本变动,请以仓库最新内容为准。 本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。

本文对照的是同一项目内的两种用法,依据均为上述仓库内容,不对两种用法做优劣排名, 选型结论只在仓库文档写明的能力边界内成立。

Playwright 会启动真实浏览器并执行页面脚本,安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。