時計
はじめに
アプリケーションの正確性を検証するには、時間依存の動作を正確にシミュレーションすることが不可欠です。 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>
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();
時間 manually を刻み、すべてのタイマーを常に起動する
まれに、時間を手動で刻み、すべてのタイマーとアニメーションフレームをプロセス内で起動して、時間の経過を細かく制御したい場合があります。
<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');