メインコンテンツへスキップ

時刻

概要

時間依存の動作を正確にシミュレートすることは、アプリケーションの正確性を検証するために不可欠です。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 を呼び出す場合、その呼び出しは他のクロック関連の呼び出し(リストについては上記の注意を参照)よりも前に発生する必要があります。これらのメソッドを順不同で呼び出すと、未定義の動作が発生します。たとえば、install はクロック関数のネイティブ定義を上書きするため、setInterval を呼び出した後、install を呼び出し、次に clearInterval を呼び出すことはできません。

事前定義された時間でのテスト

多くの場合、タイマーを進行させながら 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>
// 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.InstallAsync(new()
{
TimeDate = new DateTime(2024, 2, 2, 8, 0, 0)
});
await Page.GotoAsync("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.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));

// Assert the page state.
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM");

// Close the laptop lid again and open it at 10:30am.
await Page.Clock.FastForwardAsync("30:00");
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("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.InstallAsync();
await page.GotoAsync("http://localhost:3333");

// Interact with the page
await page.GetByRole("button").ClickAsync();

// 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.FastForwardAsync("05:00");

// Check that the user was logged out automatically.
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>
// Initialize clock with a specific time, let the page load naturally.
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");

// Pause the time flow, stop the timers, you now have manual control
// over the page time.
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
await Expect(locator).ToHaveTextAsync("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.RunForAsync(2000);
await Expect(locator).ToHaveTextAsync("2/2/2024, 10:00:02 AM");