跳到主要内容

时钟功能

简介

准确模拟时间相关行为对于验证应用程序的正确性至关重要。利用 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>
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');

await page.clock.setFixedTime(new Date('2024-02-02T10:30:00'));
// 我们知道页面有一个每秒更新时间一次的计时器
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');

一致的时间与计时器

有时您的计时器依赖于 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.install({ time: new Date('2024-02-02T08:00:00') });
await page.goto('http://localhost:3333');

// 模拟用户在上午10点合上并重新打开笔记本电脑
// 当到达该时间点时暂停时间
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');

// 再次合上笔记本电脑并在上午10:30打开
await page.clock.fastForward('30:00');
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:30:00 AM');

测试无活动监控

无活动监控是 Web 应用程序中的常见功能,它会在用户一段时间不活动后自动登出。测试这个功能可能比较棘手,因为需要等待很长时间才能看到效果。借助时钟功能,你可以加速时间并快速测试此功能。

<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.install();
await page.goto('http://localhost:3333');
// 与页面交互
await page.getByRole('button').click();

// 快进5分钟,模拟用户没有任何操作
// 快进就像合上笔记本盖子5分钟后再次打开
// 所有到期的定时器会立即触发,就像在真实浏览器中一样
await page.clock.fastForward('05:00');

// 检查用户是否被自动登出
await expect(page.getByText('You have been logged out due to inactivity.')).toBeVisible();

手动推进时间并触发所有计时器

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

<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.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'));
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:00 AM');

// 手动推进时间,在此过程中触发所有计时器
// 在此示例中,屏幕上的时间将更新2次
await page.clock.runFor(2000);
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:02 AM');

相关视频