时钟功能

简介

准确模拟依赖时间的行为对于验证应用程序的正确性至关重要。利用时钟功能,开发人员可以在测试中操作和控制时间,从而精确验证渲染时间、超时、计划任务等功能,而无需受实际时间执行的延迟和不确定性影响。

时钟 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

使用预定义时间进行测试

通常,你只需要伪造 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'));
// We know that the page has a timer that updates the time every second.
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>
// Initialize clock with some time before the test time and let the page load
// naturally. `Date.now` will progress as the timers fire.
await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
await page.goto('http://localhost:3333');

// Pretend that the user closed the laptop lid and opened it again at 10am,
// Pause the time once reached that point.
await page.clock.pauseAt(new Date('2024-02-02T10:00:00'));

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

// Close the laptop lid again and open it at 10:30am.
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>
// Initial time does not matter for the test, so we can pick current time.
await page.clock.install();
await page.goto('http://localhost:3333');
// Interact with the page
await page.getByRole('button').click();

// Fast forward time 5 minutes as if the user did not do anything.
// Fast forward is like closing the laptop lid and opening it after 5 minutes.
// All the timers due will fire once immediately, as in the real browser.
await page.clock.fastForward('05:00');

// Check that the user was logged out automatically.
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>
// Initialize clock with a specific time, let the page load naturally.
await page.clock.install({ time: new Date('2024-02-02T08:00:00') });
await page.goto('http://localhost:3333');

// Pause the time flow, stop the timers, you now have manual control
// over the page time.
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');

// Tick through time manually, firing all timers in the process.
// In this case, time will be updated in the screen 2 times.
await page.clock.runFor(2000);
await expect(page.getByTestId('current-time')).toHaveText('2/2/2024, 10:00:02 AM');