首页 / Playwright 入门教程 / 权限、地理位置与颜色方案模拟

Playwright 入门教程

权限、地理位置与颜色方案模拟

本教程共 59 篇 · 第 28 篇 · 更新于 2026-08-04 · 约 10 分钟阅读

PlaywrightpermissionsgeolocationcolorSchemeemulateMedialocaletimezone

28. 权限、地理位置与颜色方案模拟

本节目标:会预授权浏览器权限、伪造定位坐标、切换暗色模式和打印样式,把测试从「弹窗拦路」的困境里解放出来。

上一章模拟的是设备形态。这一章模拟的是环境属性——用户在哪、用什么语言、系统是不是暗色、给没给权限。

这些东西平时不起眼,真要测起来处处是坑。比如页面一加载就弹「是否允许获取位置」,脚本立刻卡住。

权限:提前给,别等它问

浏览器权限弹窗是原生 UI,脚本点不到。解法是提前授权,让弹窗根本不出现。

在配置里给整个项目授权:

// playwright.config.ts
export default defineConfig({
  use: {
    permissions: ['notifications'],
  },
});

库模式在建 context 时给:

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

也可以在测试里动态给:

test.beforeEach(async ({ context }) => {
  await context.grantPermissions(['notifications']);
});

test('推送提醒', async ({ page }) => {
  // 页面调 Notification.requestPermission() 会直接拿到 granted
});

限定到某个域名

不加限制的话,权限对所有域名生效。想更精确,指定 origin

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

这在测「跨域时权限不继承」这类场景时有用。

撤销权限

await context.clearPermissions();

注意它是全部清空,没有单独撤销某一项的方法。要测「用户拒绝了权限」,就在建 context 时不给这项权限,让页面走拒绝分支。

常见权限名

'geolocation'          // 地理位置
'notifications'        // 桌面通知
'camera'               // 摄像头
'microphone'           // 麦克风
'clipboard-read'       // 读剪贴板
'clipboard-write'      // 写剪贴板
'midi'                 // MIDI 设备
'background-sync'      // 后台同步
'accelerometer'        // 加速度计
'payment-handler'      // 支付处理
Note

不同浏览器内核支持的权限项不完全一样。Chromium 支持最全,WebKit 和 Firefox 有些项会直接报错。跨浏览器项目里按内核分别配置比较稳妥。

地理位置:伪造坐标

测「附近的门店」「同城配送」这类功能,需要伪造定位。

关键点:光设坐标不够,还得给 geolocation 权限。少一个都不生效。

export default defineConfig({
  use: {
    geolocation: { longitude: 120.155, latitude: 30.274 },  // 杭州
    permissions: ['geolocation'],
  },
});

在测试文件里用:

test.use({
  geolocation: { longitude: 116.397, latitude: 39.909 },  // 北京
  permissions: ['geolocation'],
});

test('显示附近门店', async ({ page }) => {
  await page.goto('https://example.com/stores');
  await expect(page.getByText('北京')).toBeVisible();
});

测试中途改位置

模拟用户移动,用 setGeolocation()

test.use({
  geolocation: { longitude: 116.397, latitude: 39.909 },
  permissions: ['geolocation'],
});

test('位置变化后刷新列表', async ({ page, context }) => {
  await page.goto('https://example.com/stores');
  await expect(page.getByText('北京')).toBeVisible();

  // 移动到上海
  await context.setGeolocation({ longitude: 121.474, latitude: 31.230 });
  await page.reload();
  await expect(page.getByText('上海')).toBeVisible();
});
Warning

setGeolocation() 挂在 context 上,改的是整个上下文的位置,不能只改某一个页面。同 context 下的所有标签页会一起变。

坐标格式记牢:longitude 是经度,latitude 是纬度。这俩顺序写反是常见错误,结果就是定位跑到南极洲去了。还可以加精度:

{ longitude: 120.155, latitude: 30.274, accuracy: 50 }  // 精度 50 米

颜色方案:暗色模式

现在几乎所有产品都有暗色模式。测它只需要一个配置项:

export default defineConfig({
  use: {
    colorScheme: 'dark',
  },
});

支持的值是 'light''dark''no-preference'(不表达偏好)。

按测试文件切换:

test.use({ colorScheme: 'dark' });

test('暗色模式下的首页', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(18, 18, 18)');
});

它对应的是 CSS 媒体查询 prefers-color-scheme,所以只对用媒体查询实现的暗色模式生效。如果你的产品是靠给 <html> 加 class 切换的,那得用别的办法测(比如点切换按钮)。

运行中切换用 emulateMedia()

await page.emulateMedia({ colorScheme: 'dark' });
await expect(page.getByTestId('theme-icon')).toHaveAttribute('data-theme', 'dark');

await page.emulateMedia({ colorScheme: 'light' });
await expect(page.getByTestId('theme-icon')).toHaveAttribute('data-theme', 'light');

一个测试里切来切去,比开两个测试省事。

emulateMedia:不止颜色

page.emulateMedia() 能模拟一整组媒体特性。

打印样式

await page.emulateMedia({ media: 'print' });
await expect(page.getByRole('navigation')).toBeHidden();  // 打印时应隐藏导航

这是测打印样式表最直接的办法,不用真去打印预览。改回屏幕样式传 'screen'

动画偏好

await page.emulateMedia({ reducedMotion: 'reduce' });

对应 prefers-reduced-motion。用来验证「系统开启了减少动画时,页面不播放动效」——这是可访问性的基本要求。

强制颜色模式

await page.emulateMedia({ forcedColors: 'active' });

对应 forced-colors,模拟 Windows 高对比度模式。

对比度偏好

await page.emulateMedia({ contrast: 'more' });

恢复默认,传 null

await page.emulateMedia({ colorScheme: null, media: null });

语言与时区

这两个经常一起用,模拟不同地区的用户:

export default defineConfig({
  use: {
    locale: 'zh-CN',
    timezoneId: 'Asia/Shanghai',
  },
});

locale 影响:

  • Accept-Language 请求头(服务端据此返回不同语言的内容)
  • navigator.language
  • Intl 相关 API 的默认行为(日期、数字、货币格式化)

timezoneId 影响 Date 对象的本地时间计算,要用 IANA 时区标识符,比如 Asia/ShanghaiAmerica/New_YorkEurope/Berlin

test.use({
  locale: 'de-DE',
  timezoneId: 'Europe/Berlin',
});

test('德语环境下的日期格式', async ({ page }) => {
  await page.goto('https://example.com');
  // 页面上的日期应该是 DD.MM.YYYY 格式
  await expect(page.getByTestId('date')).toHaveText(/\d{2}\.\d{2}\.\d{4}/);
});
Note

这两个只改浏览器的语言和时区,不改 Node.js 那边的。测试代码里 new Date() 用的还是运行机器的时区。

需要改测试进程的时区,用 TZ 环境变量启动:

# macOS / Linux / Git Bash
TZ=Asia/Shanghai npx playwright test

# Windows PowerShell
$env:TZ="Asia/Shanghai"; npx playwright test

离线与禁用 JavaScript

两个降级场景开关。

离线模式,测断网提示和离线缓存:

export default defineConfig({
  use: { offline: true },
});

更常用的是运行中切换:

test('断网时显示提示', async ({ page, context }) => {
  await page.goto('https://example.com');

  await context.setOffline(true);
  await page.getByRole('button', { name: '刷新' }).click();
  await expect(page.getByText('网络连接已断开')).toBeVisible();

  await context.setOffline(false);
});

禁用 JavaScript,测无 JS 时的降级渲染:

test.use({ javaScriptEnabled: false });

test('无 JS 时服务端渲染内容可见', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page.getByRole('heading')).toBeVisible();
});

这个开关对 SSR 站点和渐进增强的验证挺有用。注意关了 JS 之后,很多交互测试就跑不了了,一般单独开一个项目跑这类用例。

组合起来用

实际项目里这些配置经常一起出现:

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

export default defineConfig({
  projects: [
    {
      name: 'zh-CN-light',
      use: {
        ...devices['Desktop Chrome'],
        locale: 'zh-CN',
        timezoneId: 'Asia/Shanghai',
        colorScheme: 'light',
        geolocation: { longitude: 120.155, latitude: 30.274 },
        permissions: ['geolocation', 'notifications'],
      },
    },
    {
      name: 'zh-CN-dark',
      use: {
        ...devices['Desktop Chrome'],
        locale: 'zh-CN',
        timezoneId: 'Asia/Shanghai',
        colorScheme: 'dark',
      },
    },
  ],
});
Tip

这些配置都挂在 BrowserContext 上(回想第 24 章的三层模型)。所以 test.use() 改它们时,是在告诉运行器「给这个测试建 context 时用这些参数」,而不是运行中修改。

想在测试运行到一半时改,只有少数几个有对应方法:setGeolocation()setOffline()emulateMedia()grantPermissions()。其余的必须在建 context 时定。

小结

  • 权限提前授予,别让原生弹窗有机会出现;clearPermissions() 是全部清空。
  • 地理位置必须同时geolocationpermissions: ['geolocation']
  • 经度 longitude 在前、纬度 latitude 在后,别写反。
  • 暗色模式用 colorScheme,只对 prefers-color-scheme 媒体查询生效。
  • emulateMedia() 还能模拟打印样式、减少动画、强制颜色,是可访问性测试的常用工具。
  • localetimezoneId 只改浏览器,测试进程时区要靠 TZ 环境变量。
  • offlinejavaScriptEnabled 用来测降级场景。
  • 这些都是 context 级配置,运行中能改的只有少数几个。

下一章讲最后一种模拟,也是最有意思的一种:时间。