クロック
はじめに
アプリケーションの正確性を検証するためには、時間依存の動作を正確にシミュレートすることが不可欠です。クロック機能を利用することで、開発者はテスト内で時間を操作および制御でき、リアルタイム実行の遅延や変動なしに、レンダリング時間、タイムアウト、スケジュールされたタスクなどの機能を正確に検証できます。
The クロック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.
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-02-02T08:00:00")));
page.navigate("http://localhost:3333");
Locator locator = page.getByTestId("current-time");
// Pretend that the user closed the laptop lid and opened it again at 10am.
// Pause the time once reached that point.
page.clock().pauseAt(format.parse("2024-02-02T10:00:00"));
// Assert the page state.
assertThat(locator).hasText("2/2/2024, 10:00:00 AM");
// Close the laptop lid again and open it at 10:30am.
page.clock().fastForward("30:00");
assertThat(locator).hasText("2/2/2024, 10:30:00 AM");
非アクティブ監視のテスト
非アクティブ監視は、ウェブアプリケーションでユーザーが一定期間操作がない場合にログアウトさせる一般的な機能です。この機能のテストは、効果を見るために長時間待つ必要があるため、難しい場合があります。クロックの助けを借りることで、時間を早送りしてこの機能を素早くテストできます。
<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.
page.clock().install();
page.navigate("http://localhost:3333");
Locator locator = page.getByRole("button");
// Interact with the page
locator.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.
page.clock().fastForward("05:00");
// Check that the user was logged out automatically.
assertThat(page.getByText("You have been logged out due to inactivity.")).isVisible();
すべてのタイマーを一貫して実行しながら、手動で時間を進める
まれなケースですが、時間の経過を細かく制御するために、手動で時間を進め、その過程ですべてのタイマーとアニメーションフレームを実行したい場合があります。
<div id="current-time" data-testid="current-time"></div>
<script>
const renderTime = () => {
document.getElementById('current-time').textContent =
new Date().toLocaleString();
};
setInterval(renderTime, 1000);
</script>
SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
// Initialize clock with a specific time, let the page load naturally.
page.clock().install(new Clock.InstallOptions()
.setTime(format.parse("2024-02-02T08:00:00")));
page.navigate("http://localhost:3333");
Locator locator = page.getByTestId("current-time");
// Pause the time flow, stop the timers, you now have manual control
// over the page time.
page.clock().pauseAt(format.parse("2024-02-02T10:00:00"));
assertThat(locator).hasText("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.
page.clock().runFor(2000);
assertThat(locator).hasText("2/2/2024, 10:00:02 AM");