時刻操作
はじめに
アプリケーションの正確性を検証するには、時間依存の動作を正確にシミュレートすることが不可欠です。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.
page.clock.install(time=datetime.datetime(2024, 2, 2, 8, 0, 0))
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.
page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
# Assert the page state.
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
# Close the laptop lid again and open it at 10:30am.
page.clock.fast_forward("30:00")
expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:30:00 AM")
# 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=datetime.datetime(2024, 2, 2, 8, 0, 0))
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.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
# Assert the page state.
await expect(page.get_by_test_id("current-time")).to_have_text("2/2/2024, 10:00:00 AM")
# Close the laptop lid again and open it at 10:30am.
await page.clock.fast_forward("30:00")
await expect(page.get_by_test_id("current-time")).to_have_text("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.
page.clock.install()
page.goto("http://localhost:3333")
# Interact with the page
page.get_by_role("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.
page.clock.fast_forward("05:00")
# Check that the user was logged out automatically.
expect(page.get_by_text("You have been logged out due to inactivity.")).to_be_visible()
# 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.get_by_role("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.fast_forward("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.
page.clock.install(
time=datetime.datetime(2024, 2, 2, 8, 0, 0, tzinfo=datetime.timezone.pst),
)
page.goto("http://localhost:3333")
locator = page.get_by_test_id("current-time")
# Pause the time flow, stop the timers, you now have manual control
# over the page time.
page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
expect(locator).to_have_text("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.run_for(2000)
expect(locator).to_have_text("2/2/2024, 10:00:02 AM")
# Initialize clock with a specific time, let the page load naturally.
await page.clock.install(time=
datetime.datetime(2024, 2, 2, 8, 0, 0, tzinfo=datetime.timezone.pst),
)
await page.goto("http://localhost:3333")
locator = page.get_by_test_id("current-time")
# Pause the time flow, stop the timers, you now have manual control
# over the page time.
await page.clock.pause_at(datetime.datetime(2024, 2, 2, 10, 0, 0))
await expect(locator).to_have_text("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.run_for(2000)
await expect(locator).to_have_text("2/2/2024, 10:00:02 AM")