IKivan-kozenko -aqa
Try yourself as a QA tester
All topics·Advanced·17 / 24

Clock

"Auto-logout after 30 minutes of inactivity" — how do you test that without actually waiting 30 minutes? You fake the clock. Playwright can freeze, fast-forward, or manually tick browser time.

What the clock API controls

Playwright's page.clock replaces the browser's time functions: Date, Date.now(), setTimeout, setInterval, requestAnimationFrame. When you control the clock, timers don't fire in real time — they fire when you tell them to. This makes time-dependent tests instant.

Three modes: setFixedTime (simplest — freeze Date.now to a specific timestamp), install with fastForward or pauseAt (for more complex scenarios), and runFor (manually tick milliseconds).

setFixedTime — freeze the date

The simplest case: you need Date.now() to return a specific value so that dates display consistently in tests. If your order list shows "Created 2 days ago" and the text depends on the current date — tests become flaky because "2 days ago" changes every day. Fix: freeze the clock before loading the page.

ts
test('order shows correct relative date', async ({ page }) => {
  // Фіксуємо час ПЕРЕД завантаженням сторінки
  await page.clock.setFixedTime(new Date('2026-05-14T12:00:00'))

  await page.goto('/orders')

  // Замовлення створене 2026-05-12 завжди показуватиме "2 days ago"
  await expect(page.getByTestId('order-date').first()).toContainText('2 days ago')
})

test('session expiry shows correct countdown', async ({ page }) => {
  await page.clock.setFixedTime(new Date('2026-05-14T09:00:00'))
  await page.goto('/dashboard')

  // Сесія закінчується о 17:00 — показує "8 hours remaining"
  await expect(page.getByTestId('session-expiry')).toContainText('8 hours remaining')
})

fastForward — skip through time

For features like auto-logout, session expiry, or debounced search — where something should happen after a time delay — fastForward fires all pending timers instantly. It's like closing the laptop lid and opening it N minutes later.

ts
test('auto-logout after 30 minutes inactivity', async ({ page }) => {
  await page.clock.install()
  await page.goto('/dashboard')

  // Перевіряємо що залогінені
  await expect(page.getByTestId('user-menu')).toBeVisible()

  // Перемотуємо 30 хвилин — жодного реального очікування
  await page.clock.fastForward('30:00')

  // Застосунок має перенаправити на логін
  await expect(page).toHaveURL('/login')
  await expect(page.getByText('Session expired')).toBeVisible()
})

test('debounced search fires after 500ms', async ({ page }) => {
  await page.clock.install()
  await page.goto('/orders')

  await page.getByRole('searchbox', { name: 'Search orders' }).fill('laptop')

  // Без перемотки — запит ще не відправлений (debounce 500ms)
  // Перемотуємо 500ms
  await page.clock.fastForward(500)

  // Тепер запит має відправитися
  await expect(page.getByRole('row')).toHaveCount(3)
})

pauseAt — stop at a specific moment

pauseAt lets you install the clock, let the page load naturally (with timers firing), and then freeze time at a specific timestamp. Useful when you need to test a specific point in time — like what happens at exactly midnight, or at a deadline.

ts
test('countdown shows zero at deadline', async ({ page }) => {
  // Починаємо задовго до дедлайну
  await page.clock.install({ time: new Date('2026-05-14T08:00:00') })
  await page.goto('/orders/42')

  // Перевіряємо початковий countdown
  await expect(page.getByTestId('delivery-countdown')).toContainText('9 hours remaining')

  // Переходимо до моменту дедлайну
  await page.clock.pauseAt(new Date('2026-05-14T17:00:00'))
  await expect(page.getByTestId('delivery-countdown')).toContainText('Deadline passed')
})

runFor — tick manually

runFor ticks time by an exact number of milliseconds, firing each timer that falls within that window in order. Unlike fastForward (which jumps), runFor steps through — useful when you need to verify intermediate states between timer firings.

ts
test('progress bar updates every second', async ({ page }) => {
  await page.clock.install({ time: new Date('2026-05-14T10:00:00') })
  await page.clock.pauseAt(new Date('2026-05-14T10:00:00'))

  await page.goto('/import-job/123')
  await expect(page.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '0')

  // Tick 1 секунду — перший апдейт прогресу
  await page.clock.runFor(1000)
  await expect(page.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '10')

  // Ще 4 секунди
  await page.clock.runFor(4000)
  await expect(page.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '50')
})