模拟设备、语言、时区、权限:Playwright 的 emulation 能改哪些环境变量
一个很常见的场景:一组用例在你本机全绿,推到 CI 上有两条挂了,报错是日期断言对不上;再往下查,发现同事那台机器上另一条也挂,因为页面按浏览器语言渲染成了英文而断言写的是中文。这类问题的共同点是——用例依赖了运行机器的环境,而这些环境在每台机器上都不一样。
Playwright 仓库里 docs/src/emulation.md 这一页讲的就是把这些环境量从「跟机器走」改成「跟配置走」。它列出的可模拟项包括 userAgent、screen、viewport、hasTouch、geolocation、locale、timezone、permissions、colorScheme 等。下面按落地顺序走一遍,重点放在一件事上:这几组选项的作用范围不一样,混着用最容易踩坑。
前置条件:先确认你用的是哪种形态、哪个语言绑定
这一步不能跳,因为写法完全不同。
第一,Test Runner 和 Library 两种形态的入口不同。用 @playwright/test 时,这些选项写在 playwright.config.ts 的 use 里,或者在测试文件里用 test.use({...});直接用 Playwright 库时,它们是 browser.newContext() 的参数。docs/src/emulation.md 里的代码块正是按 tab=js-test 和 tab=js-library 分开标注的,抄的时候别抄串了。
第二,设备预设不是所有语言绑定都有。docs/src/api/class-playwright.md 里 Playwright.devices 这个属性出现了两次,分别标 langs: js, python 和 langs: csharp,没有 java。对应地,docs/src/emulation.md 的 Devices 一节也拆成了两块:标 langs: js, csharp, python 的那块讲 devices 注册表;标 langs: java 的那块则写明 Java 要在 Browser.newContext 上自己指定 setDeviceScaleFactor、setHasTouch、setIsMobile、setScreenSize、setUserAgent、setViewportSize 这几个选项。
第三,选项在 Test Runner 里的可用起点,文档里有 since 标注可查。比如 docs/src/test-api/class-testoptions.md 中 TestOptions.locale 标注 since: v1.10,而 docs/src/api/class-browsercontext.md 里 BrowserContext.grantPermissions 标注 since: v1.8。项目迭代频繁,这些标注以仓库最新内容为准。
devices 预设:一次性铺一组 context 选项
devices 是一张注册表,文档指向仓库里的 packages/isomorphic/deviceDescriptorsSource.json。打开这个 JSON 会看到每个条目就是一组 context 选项,出现过的字段一共就这几个:userAgent、viewport、deviceScaleFactor、isMobile、hasTouch、defaultBrowserType,另有一部分条目额外带 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。
因为预设本身也定义了 viewport 和 isMobile,所以要覆盖它们,顺序很重要:文档的示例注释明写,viewport 和 isMobile 这类属性必须写在解构 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.md 的 context-option-locale 写的是「Defaults to the system default locale」,而 docs/src/test-api/class-testoptions.md 的 TestOptions.locale 写的是「Defaults to en-US」。也就是说同一个选项名,在库形态和 Test Runner 形态下,文档给出的默认值不一样。这是仓库当前文档里的默认值,随版本可能变动,但如果你从库形态迁到 Test Runner,这一处值得先确认一遍。
作用范围上最需要记住的是:docs/src/api/class-browsercontext.md 与 docs/src/api/class-page.md 里没有 setLocale 或 setTimezoneId 这类方法——不像 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.md 在 grantPermissions 的参数说明处挂了一个 :::danger 块,原话是不同浏览器之间、甚至同一浏览器的不同版本之间,支持的权限并不相同,任何一项权限都可能在一次更新之后不再工作,随后才列出「可能被某些浏览器支持」的权限名。所以这一列权限名不是承诺清单,写用例时按你实际用到的那一两个来,别照单全抄。
geolocation:能改,但只能整个 context 一起改
地理位置的选项形状在 docs/src/api/params.md 里写明是一个对象,字段为 latitude(-90 到 90)、longitude(-180 到 180),以及可选的 accuracy(非负,默认 0,这是仓库当前文档里的默认值,随版本可能变动)。
关键是两条约束。第一,它和 permissions 是配套的:BrowserContext.setGeolocation 的文档挂了一条 Note,建议用 grantPermissions 给 context 里的页面授予读取地理位置的权限。所以文档示例里 geolocation 和 permissions: ['geolocation'] 总是一起出现。第二,docs/src/emulation.md 在改位置那一段后面直接写明:你只能为 context 里的所有 page 一起改地理位置,没有 page 粒度。
运行中改位置用 BrowserContext.setGeolocation,文档还写明传 null 或 undefined 表示模拟「位置不可用」——这一条对测异常分支很有用。
边界:文档和测试里已经写明不成立的情况
isMobile:docs/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.ts用navigator.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 签名、配置项与默认值随版本变动,请以仓库最新内容为准。
本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。