跳到主要内容

Clock(时钟)

简介

准确模拟与时间相关的行为对于验证应用程序的正确性至关重要。利用 Clock 功能,开发人员可以在测试中操纵和控制时间,从而能够精确验证诸如渲染时间、超时、定时任务等功能,而无需受实时执行的延迟和可变性影响。

Clock API 提供以下方法来控制时间:

  • setFixedTime:为 Date.now()new Date() 设置固定时间。
  • install:初始化时钟,并允许您:
    • pauseAt:在特定时间暂停时间。
    • fastForward:快进时间。
    • runFor:运行特定时长的时间。
    • resume:恢复时间。
  • setSystemTime:设置当前系统时间。

推荐的方法是使用 setFixedTime 将时间设置为特定值。如果这不适用于您的用例,可以使用 install,它允许您稍后暂停时间、快进时间、推进时间等。setSystemTime 仅推荐用于高级用例。

备注

Page.Clock 会覆盖与时间相关的原生全局类和函数,使其能够被手动控制:

  • Date
  • setTimeout
  • clearTimeout
  • setInterval
  • clearInterval
  • requestAnimationFrame
  • cancelAnimationFrame
  • requestIdleCallback
  • cancelIdleCallback
  • performance
  • Event.timeStamp
注意

如果在测试中的任何时候调用 install,该调用 必须 发生在任何其他与时钟相关的调用之前(有关列表,请参阅上面的注释)。以错误的顺序调用这些方法将导致未定义行为。例如,不能先调用 setInterval,再调用 install,然后调用 clearInterval,因为 install 会覆盖时钟函数的原生定义。

使用预定义时间进行测试

通常,你只需要伪造 Date.now,同时让定时器继续运行。这样时间会自然流逝,但 Date.now 始终返回一个固定值。

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

一致的时间和定时器

有时,你的定时器依赖于 Date.now,当 Date.now 的值不随时间变化时,定时器会感到困惑。在这种情况下,你可以安装时钟,并在测试时快进到感兴趣的时间。

<div id="current-time" data-testid="current-time"></div>
<script>
const renderTime = () => {
document.getElementById('current-time').textContent =
new Date().toLocaleString();
};
setInterval(renderTime, 1000);
</script>
// 在测试时间之前的某个时间初始化时钟,让页面自然加载。
// 当定时器触发时,`Date.now` 将随之推进。
await Page.Clock.InstallAsync(new()
{
TimeDate = new DateTime(2024, 2, 2, 8, 0, 0)
});
await Page.GotoAsync("http://localhost:3333");

// 假设用户合上笔记本电脑盖子,然后在上午 10 点再次打开。
// 一旦到达该时间点,暂停时间。
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));

// 断言页面状态。
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM");

// 再次合上笔记本电脑盖子,并在上午 10:30 打开。
await Page.Clock.FastForwardAsync("30:00");
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:30:00 AM");

测试活动监控

活动监控是 Web 应用程序中的一项常见功能,它会在用户一段时间不活动后将其注销。测试此功能可能会有些棘手,因为你需要等待很长时间才能看到效果。借助时钟(clock),你可以加快时间流逝,快速测试此功能。

<div id="remaining-time" data-testid="remaining-time"></div>
<script>
const endTime = Date.now() + 5 * 60_000;
const renderTime = () => {
const diffInSeconds = Math.round((endTime - Date.now()) / 1000);
if (diffInSeconds <= 0) {
document.getElementById('remaining-time').textContent =
'You have been logged out due to inactivity.';
} else {
document.getElementById('remaining-time').textContent =
`You will be logged out in ${diffInSeconds} seconds.`;
}
setTimeout(renderTime, 1000);
};
renderTime();
</script>
<button type="button">Interaction</button>
// 初始时间对测试无关紧要,因此我们可以选择当前时间。
await Page.Clock.InstallAsync();
await page.GotoAsync("http://localhost:3333");

// 与页面进行交互
await page.GetByRole("button").ClickAsync();

// 将时间快进 5 分钟,就好像用户什么也没做一样。
// 快进就像合上笔记本电脑盖子,5 分钟后再打开。
// 所有到期的定时器将立即一次性触发,就像在真实浏览器中一样。
await Page.Clock.FastForwardAsync("05:00");

// 检查用户是否已自动注销。
await Expect(Page.GetByText("You have been logged out due to inactivity.")).ToBeVisibleAsync();

手动推进时间,持续触发所有定时器

在极少数情况下,你可能希望手动推进时间,在此过程中触发所有定时器和动画帧,以实现对时间流逝的精细控制。

<div id="current-time" data-testid="current-time"></div>
<script>
const renderTime = () => {
document.getElementById('current-time').textContent =
new Date().toLocaleString();
};
setInterval(renderTime, 1000);
</script>
// 使用特定时间初始化时钟,让页面自然加载。
await Page.Clock.InstallAsync(new()
{
TimeDate = new DateTime(2024, 2, 2, 8, 0, 0, DateTimeKind.Pst)
});
await page.GotoAsync("http://localhost:3333");
var locator = page.GetByTestId("current-time");

// 暂停时间流动,停止定时器,现在你可以手动控制
// 页面时间。
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:00 AM");

// 手动推进时间,在此过程中触发所有定时器。
// 在这种情况下,屏幕上的时间将更新2次。
await Page.Clock.RunForAsync(2000);
await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:02 AM");

相关视频