首页 / Playwright 入门教程 / 时钟模拟:Clock 冻结与快进时间

Playwright 入门教程

时钟模拟:Clock 冻结与快进时间

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

PlaywrightclocksetFixedTimeinstallfastForward时间模拟计时器

29. 时钟模拟:Clock 冻结与快进时间

本节目标:能用 page.clock 把页面里的时间「冻住」或「快进」,从而快速测出倒计时、超时、定时任务这些行为,而不必真实等待几分钟几小时。

有些功能和时间死死绑在一起。比如登录后五分钟不动就自动登出,比如一个倒计时显示「还剩 30 秒」,比如页面每隔一秒刷新一次时间。

如果老老实实等真实时间,测一个自动登出就得干坐五分钟。而且真实时间有抖动,今天跑过明天未必过。Clock(时钟)就是专门解决这件事的:它让你在测试里手动控制时间,想让它几点就几点,想快进就快进。

Clock 到底接管了什么

调用 page.clock 的相关方法后,Playwright 会把页面里一批和时间有关的原生函数换成自己实现的控制版本。被接管的包括:

  • Date(所以 new Date()Date.now() 都听 Clock 的)
  • setTimeout / clearTimeout
  • setInterval / clearInterval
  • requestAnimationFrame / cancelAnimationFrame
  • requestIdleCallback / cancelIdleCallback
  • performance(性能计时也归 Clock 管)
  • Event.timeStamp(事件时间戳同样受控)

一句话:凡是页面里「靠时间驱动」的东西,一旦装了 Clock,都由你说了算。

补充一点:Clock 是装在 BrowserContext(浏览器上下文)上的,同一 context 下的所有页面、iframe 时间都归它管。所以你在某个页面上拨快了时间,同 context 里的其他页面也会一起变。

三种武器,先选对

Playwright 给你三档能力,由浅到深:

  1. setFixedTime:只把 Date.now() 钉成一个固定值,计时器照常走。最常用、最省心。
  2. install:完全接管时间,之后你能暂停、快进、手动 tick(步进)。功能最强。
  3. setSystemTime:把系统时间拨到某一刻,但不触发任何计时器。官方只推荐给进阶场景。

记住一个总原则:setFixedTime 解决就别用 install。前者简单不出错,后者控制力强但坑也多。

Tip

大多数「页面显示当前时间」的需求,setFixedTime 就够了。只有当你要测「用户离开 5 分钟被登出」这种需要时间往前走很久的逻辑,才上 install

setFixedTime:只钉时间,计时器不停

setFixedTimeDate.now()new Date() 钉死成一个固定时刻。注意它不会停掉 setInterval / setTimeout,计时器依旧每秒触发,只是它们读到的时间永远是你钉住的值。

看个直观例子。页面里有段脚本每秒把当前时间渲染到屏幕:

<div data-testid="current-time"></div>
<script>
  const renderTime = () => {
    document.getElementById('current-time').textContent =
      new Date().toLocaleString();
  };
  setInterval(renderTime, 1000);
</script>

测试里这样钉时间:

import { test, expect } from '@playwright/test';

test('时间被钉住,但每秒仍在刷新', async ({ page }) => {
  // 把 Date.now 钉到 10:00:00
  await page.clock.setFixedTime(new Date('2024-02-02T10:00:00'));
  await page.goto('http://localhost:3333');

  await expect(page.getByTestId('current-time'))
    .toHaveText('2/2/2024, 10:00:00 AM');

  // 再钉到 10:30:00
  await page.clock.setFixedTime(new Date('2024-02-02T10:30:00'));

  // 页面里的 setInterval 每秒会再渲染一次,读到新时间
  await expect(page.getByTestId('current-time'))
    .toHaveText('2/2/2024, 10:30:00 AM');
});

这里容易绕晕:为什么「时间固定」了,文本还能从 10:00 变成 10:30?关键在于 setInterval 没被停,它每隔一秒还是触发 renderTimerenderTime 读的是 new Date()——也就是被你钉住的值。你第二次 setFixedTime 把钉的值改成 10:30,下一次计时器触发时自然就渲染出 10:30 了。

所以准确的描述是:时间值是固定的,但计时器的触发节奏是正常的

install:完全接管时间

setFixedTime 管不了计时器「走了多久」。如果你想让时间真正往前走,比如快进 5 分钟去触发登出,就得用 install

install 一调用,时间就归你控制,页面里所有计时器都按你给的节奏走。它有几个配套动作:

动作作用
pauseAt(time)让时间走到 time 那一刻后暂停,计时器停住
fastForward(duration)像合上笔记本又打开,把时间快进一段,期间该触发的计时器一次性补触发
runFor(duration)手动逐步推进时间,过程中所有计时器按顺序触发
resume()恢复正常流动,时间继续自然走

pauseAt:走到某点就停

test('时间走到 10 点暂停', async ({ page }) => {
  // 先用某个起点初始化时钟,页面照常加载
  await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
  await page.goto('http://localhost:3333');

  // 让时间走到 10:00:00 然后暂停
  await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));

  await expect(page.getByTestId('current-time'))
    .toHaveText('2/2/2024, 10:00:00 AM');
});

暂停后,页面里的 Date.now 卡在 10:00:00,计时器也停了。你这时可以安心断言页面状态。

fastForward:快进一段

接着上面的场景,想模拟「又过了 30 分钟」:

test('从 10 点快进 30 分钟', async ({ page }) => {
  await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
  await page.goto('http://localhost:3333');
  await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));

  // 快进 30 分钟,相当于合上笔记本半小时再打开
  await page.clock.fastForward('30:00');

  await expect(page.getByTestId('current-time'))
    .toHaveText('2/2/2024, 10:30:00 AM');
});

fastForward 的语义很妙:它不像你手动等 30 分钟,而是瞬间把时间推到目标点,并让途中到期的所有计时器立刻补触发——和真实浏览器里「合上盖子再打开」的表现一致。

runFor:手动一步一步走

runForfastForward 类似,但更强调「逐格推进」。比如想精确控制时间前进 2 秒、看两次渲染:

test('手动推进 2 秒', async ({ page }) => {
  await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
  await page.goto('http://localhost:3333');
  await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));

  // 手动走 2000 毫秒,期间 setInterval 会触发两次
  await page.clock.runFor(2000);

  await expect(page.getByTestId('current-time'))
    .toHaveText('2/2/2024, 10:00:02 AM');
});

runFor(2000) 让时间前进 2 秒,那个每秒渲染一次的计时器就会触发两次,屏幕时间从 10:00:00 走到 10:00:02。

resume:恢复正常流动

如果暂停后想让时间重新自然走,用 resume()

await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));
// ...断言某些状态...
await page.clock.resume(); // 时间继续按真实节奏流动

setSystemTime:直接拨系统时间(进阶)

setSystemTime 把系统时间拨到任意一刻,但一个计时器都不会触发

它的定位很窄:测「系统时间突然变了」这种场景。比如夏令时切换、用户手动改了时区,看看页面会不会算错。官方明确说它只推荐进阶用例,新手遇到「钉时间」需求优先用 setFixedTime

await page.clock.install();
await page.clock.setSystemTime(new Date('2024-02-02T10:00:00'));
Note

setSystemTime 本身不强制先 install。但只要你这个测试里用到了 install,它就必须排在所有时钟调用的最前面——原因见下一节。

install 必须在最前面:铁律

这是最容易踩的坑,务必记住。

Warning

一旦你在测试里调用了 install,这个调用必须早于任何其它时钟相关的调用。因为 install 会覆盖计时器函数的原生定义。如果顺序乱了——比如先 setInterval、再 install、再 clearInterval——就会出现未定义行为,测试可能静默失效。

简单记:install 是开门的第一句话。正确顺序永远是:

await page.clock.install({ time: new Date('2024-02-02T08:00:00') }); // 先开门
await page.goto('http://localhost:3333');
await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));           // 再操作

setFixedTime 不需要 install 打头,它自成一体,单独用即可。

一个典型知识点:测「不活动自动登出」

这类功能靠 setTimeout 倒计时,真实等五分钟太慢。用 Clock 几行就测完:

test('闲置 5 分钟自动登出', async ({ page }) => {
  // 初始时间无所谓,用当前时间即可
  await page.clock.install();
  await page.goto('http://localhost:3333');

  // 模拟用户点了一下(重置倒计时)
  await page.getByRole('button', { name: 'Interaction' }).click();

  // 快进 5 分钟,像用户一直没动
  await page.clock.fastForward('05:00');

  await expect(page.getByText('You have been logged out due to inactivity.'))
    .toBeVisible();
});

页面逻辑是:最后一次交互后 5 分钟无操作就登出。fastForward('05:00') 让那段 setTimeout 立刻到期触发,登出提示出现,断言通过。整段测试瞬间完成。

时区与语言环境去哪设

Clock 只管「时间怎么走」,不管「时间怎么显示」。页面里 toLocaleString() 显示成什么样子,取决于浏览器的时区和语言,那是上一章讲的环境模拟:

  • 时区用 contexttimezoneId(如 'Asia/Shanghai'
  • 语言用 contextlocale(如 'zh-CN'

如果你想让 setFixedTime 钉住的时刻在本地面板里显示成「北京时间」,记得在创建 context 时一并配好 timezoneId,详见第 28 章。

Note

Clock 在「库模式(Library)」下同样可用,区别只是 page 是你手动 browser.newContext()context.newPage() 拿到的,而不是测试运行器自动注入的夹具。

小结

  • 测时间相关行为,别真实等,用 page.clock 接管时间。
  • 只想钉住「当前时刻」:setFixedTime,计时器照走。
  • 想让时间能走、能快进:install 后配 pauseAt / fastForward / runFor / resume
  • install 必须是测试里第一个时钟调用,顺序错会静默出错。
  • setSystemTime 只拨时间不触发计时器,用来测夏令时、改时区这类突变。
  • 时区、语言显示归 context 的环境模拟管,Clock 不负责。

下一章开始进网络这块:先学怎么把请求拦下来。