模拟设备、语言、时区、权限:Playwright 的 emulation 能改哪些环境变量

2026-08-18

一个很常见的场景:一组用例在你本机全绿,推到 CI 上有两条挂了,报错是日期断言对不上;再往下查,发现同事那台机器上另一条也挂,因为页面按浏览器语言渲染成了英文而断言写的是中文。这类问题的共同点是——用例依赖了运行机器的环境,而这些环境在每台机器上都不一样。

Playwright 仓库里 docs/src/emulation.md 这一页讲的就是把这些环境量从「跟机器走」改成「跟配置走」。它列出的可模拟项包括 userAgentscreenviewporthasTouchgeolocationlocaletimezonepermissionscolorScheme 等。下面按落地顺序走一遍,重点放在一件事上:这几组选项的作用范围不一样,混着用最容易踩坑。

前置条件:先确认你用的是哪种形态、哪个语言绑定

这一步不能跳,因为写法完全不同。

第一,Test Runner 和 Library 两种形态的入口不同。用 @playwright/test 时,这些选项写在 playwright.config.tsuse 里,或者在测试文件里用 test.use({...});直接用 Playwright 库时,它们是 browser.newContext() 的参数。docs/src/emulation.md 里的代码块正是按 tab=js-testtab=js-library 分开标注的,抄的时候别抄串了。

第二,设备预设不是所有语言绑定都有docs/src/api/class-playwright.mdPlaywright.devices 这个属性出现了两次,分别标 langs: js, pythonlangs: csharp没有 java。对应地,docs/src/emulation.md 的 Devices 一节也拆成了两块:标 langs: js, csharp, python 的那块讲 devices 注册表;标 langs: java 的那块则写明 Java 要在 Browser.newContext 上自己指定 setDeviceScaleFactorsetHasTouchsetIsMobilesetScreenSizesetUserAgentsetViewportSize 这几个选项。

第三,选项在 Test Runner 里的可用起点,文档里有 since 标注可查。比如 docs/src/test-api/class-testoptions.mdTestOptions.locale 标注 since: v1.10,而 docs/src/api/class-browsercontext.mdBrowserContext.grantPermissions 标注 since: v1.8。项目迭代频繁,这些标注以仓库最新内容为准。

devices 预设:一次性铺一组 context 选项

devices 是一张注册表,文档指向仓库里的 packages/isomorphic/deviceDescriptorsSource.json。打开这个 JSON 会看到每个条目就是一组 context 选项,出现过的字段一共就这几个:userAgentviewportdeviceScaleFactorisMobilehasTouchdefaultBrowserType,另有一部分条目额外带 screen(不是每个条目都有,取用前先看你那一条)。它没有 locale、没有 timezoneId、没有 permissions——这是第一个容易误会的地方:展开一个设备预设并不会顺带把语言和时区也设好。

配置里的用法(原样取自 docs/src/emulation.md):

import { defineConfig, devices } from '@playwright/test'; // import devices

export default defineConfig({
  projects: [
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
      },
    },
    {
      name: 'Mobile Safari',
      use: {
        ...devices['iPhone 13'],
      },
    },
  ],
});

Python 侧走的是 playwright.devices 字典加解包:

iphone_13 = playwright.devices['iPhone 13']
browser = playwright.webkit.launch(headless=False)
context = browser.new_context(
    **iphone_13,
)

文档在这里带了一条 Note:预设是假定了具体平台的,"Desktop Chrome" 给出的是 Windows 专属的 user agent 字符串。如果你希望 user agent 跟着实际跑测试的平台走,文档建议把这个属性取消掉——JS 侧写 userAgent: undefined,Python 侧写 user_agent=None

因为预设本身也定义了 viewportisMobile,所以要覆盖它们,顺序很重要:文档的示例注释明写,viewportisMobile 这类属性必须写在解构 devices 之后。

viewport 还可以针对单个 page 改,用 Page.setViewportSize。这是本文涉及的几组选项里少数能落到 page 粒度的。

locale 与 timezoneId:建 context 时定死,之后没有改的入口

这两个是一对,作用范围也一致。docs/src/api/params.md 里写明:locale 会影响 navigator.language 的取值、请求头 Accept-Language 的取值,以及数字与日期的格式化规则;timezoneId 改的是 context 的时区,支持的 ID 参照 ICU 的时区表。

原样取自文档的配置写法:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    // Emulates the browser locale.
    locale: 'en-GB',

    // Emulates the browser timezone.
    timezoneId: 'Europe/Paris',
  },
});

这里有一处值得摆在一起看的差异:docs/src/api/params.mdcontext-option-locale 写的是「Defaults to the system default locale」,而 docs/src/test-api/class-testoptions.mdTestOptions.locale 写的是「Defaults to en-US」。也就是说同一个选项名,在库形态和 Test Runner 形态下,文档给出的默认值不一样。这是仓库当前文档里的默认值,随版本可能变动,但如果你从库形态迁到 Test Runner,这一处值得先确认一遍。

作用范围上最需要记住的是:docs/src/api/class-browsercontext.mddocs/src/api/class-page.md没有 setLocalesetTimezoneId 这类方法——不像 geolocation 那样有运行时修改入口。要换语言或时区,只能另建一个 context(Test Runner 里就是另开一个 project,或者在 test.describe 块上 test.use)。

另外 docs/src/emulation.md 有一条标 langs: js 的说明:这只影响浏览器的时区和语言,不影响 test runner 自身的时区;要改 runner 的时区,文档指向 Node 的 TZ 环境变量。

permissions:可以后补,也可以按 origin 授予,还可以清空

权限这组选项的作用范围最宽松。既能在 context 创建时给:

const context = await browser.newContext({
  permissions: ['notifications'],
});

也能在跑的过程中补发,并且可以限定到某个 origin:

await context.grantPermissions(['notifications'], { origin: 'https://skype.com' });

还能用 BrowserContext.clearPermissions 一次性撤销全部:await context.clearPermissions();

docs/src/api/class-browsercontext.mdgrantPermissions 的参数说明处挂了一个 :::danger 块,原话是不同浏览器之间、甚至同一浏览器的不同版本之间,支持的权限并不相同,任何一项权限都可能在一次更新之后不再工作,随后才列出「可能被某些浏览器支持」的权限名。所以这一列权限名不是承诺清单,写用例时按你实际用到的那一两个来,别照单全抄。

geolocation:能改,但只能整个 context 一起改

地理位置的选项形状在 docs/src/api/params.md 里写明是一个对象,字段为 latitude(-90 到 90)、longitude(-180 到 180),以及可选的 accuracy(非负,默认 0,这是仓库当前文档里的默认值,随版本可能变动)。

关键是两条约束。第一,它和 permissions 是配套的BrowserContext.setGeolocation 的文档挂了一条 Note,建议用 grantPermissions 给 context 里的页面授予读取地理位置的权限。所以文档示例里 geolocationpermissions: ['geolocation'] 总是一起出现。第二,docs/src/emulation.md 在改位置那一段后面直接写明:你只能为 context 里的所有 page 一起改地理位置,没有 page 粒度。

运行中改位置用 BrowserContext.setGeolocation,文档还写明传 nullundefined 表示模拟「位置不可用」——这一条对测异常分支很有用。

边界:文档和测试里已经写明不成立的情况

  • isMobiledocs/src/api/params.md 明写它在 Firefox 中不受支持,默认 false
  • screen:同一份文档写明它只有在 viewport 被设置时才生效
  • viewport: null:文档挂了 Note,说这会退出默认预设,让视口取决于操作系统的宿主窗口尺寸,使测试的执行变成非确定性的。C# 侧对应的是 ViewportSize.NoViewport。默认视口文档写的是 1280x720,同样是当前文档里的默认值。
  • Windows 上的 WebKit:仓库测试 tests/library/permissions.spec.ts 顶部有一行 it.fixme(({ browserName, isWindows }) => browserName === 'webkit' && isWindows, 'Permissions API is disabled on Windows WebKit')。也就是说在 Windows 上跑 WebKit 时,这组权限测试被标为已知不通过。本站读者大多在 Windows 上开发,跨浏览器矩阵里带 WebKit 的话,这一条要心里有数。Linux/macOS 侧仓库里没有对应的这条标注。
  • colorScheme:JS/Java 侧的类型是 "light"|"dark"|"no-preference",传 null 重置为系统默认;C#/Python 侧对应值写作 "null"。文档写明默认为 'light'(当前文档里的默认值,随版本可能变动)。

怎么验证真的配上了

仓库自己的测试给了最直接的判定动作,全都是在页面里读回浏览器侧的可观测量:

  • 语言:tests/library/browsercontext-locale.spec.ts 里断言 await page.evaluate(() => navigator.language)
  • 时区:tests/library/browsercontext-timezone-id.spec.ts 里断言 await page.evaluate(() => (new Intl.DateTimeFormat()).resolvedOptions().timeZone)。这份测试文件顶部还留了一句注释,说时区名在不同浏览器和平台上渲染方式不同,所以它只校验 GMT 偏移——你自己写断言时也建议避开时区显示名。
  • 权限:tests/library/permissions.spec.ts 用的是 page.evaluate(name => navigator.permissions.query({ name }).then(result => result.state), name),读回 'prompt' / 'denied' 这类状态。
  • 地理位置:tests/library/geolocation.spec.tsnavigator.geolocation.getCurrentPosition 读回坐标再比对。

配错时的报错也有据可查。时区 ID 非法时,packages/playwright-core/src/server/ 下 Chromium、Firefox、WebKit 三个后端各自都会抛出 Invalid timezone ID: 开头的错误;经纬度越界时,tests/library/geolocation.spec.ts 断言的错误信息是 geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.。看到这两类报错,说明问题出在传值本身,不用去怀疑浏览器。

一个组合起来的最小验证片段:

const context = await browser.newContext({
  locale: 'de-DE',
  timezoneId: 'Europe/Berlin',
  geolocation: { longitude: 41.890221, latitude: 12.492348 },
  permissions: ['geolocation'],
});

以上为按仓库代码中的接口语义组合的示例,未经实测,以仓库最新代码为准。上面几个具体取值都是仓库文档示例里的值,不是推荐值。

最后一句提醒:Playwright 启动的是真实浏览器、在真实页面上执行脚本,grantPermissions 授予的是浏览器侧的真实权限(相机、麦克风、剪贴板这类都在可授予之列)。在自动化里授权和在日常浏览里授权,风险性质是一样的,别因为「是测试环境」就默认没有影响。安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。


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

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