Resource library

QA How-To

Playwright mock date and time: Examples and Best Practices

Use playwright mock date and time examples for deadlines, countdowns, polling, inactivity, timezones, expiry boundaries, and deterministic Clock tests.

24 min read | 2,276 words

TL;DR

Use `setFixedTime()` for stable date-driven rendering, `runFor()` for every scheduled callback, `fastForward()` for sleep-like jumps, and `pauseAt()` for exact boundaries. Install Clock before startup, control timezone and API data separately, and assert user outcomes.

Key Takeaways

  • Match each scenario to fixed time, continuous passage, a time jump, or a wall-clock change.
  • Install Clock before page code creates timers, intervals, or animation callbacks.
  • Use explicit before, exact, and after cases for expiry and deadline rules.
  • Control API expiry data separately from the browser's current instant.
  • Configure named timezone and locale inputs for local calendar assertions.
  • Assert business state and available actions, not only formatted time text.
  • Keep exhaustive date arithmetic in unit tests and browser wiring in Playwright.

These playwright mock date and time examples are a scenario cookbook for deadlines, countdowns, inactivity, polling, debouncing, midnight boundaries, daylight-saving changes, and client-side expiry. Each example uses Playwright's supported Clock API and asserts an observable product result rather than stopping at a mocked timestamp.

Copying a Clock call is not enough. A reliable test must choose the passage model that matches real user behavior, install control before application timers start, and separate browser time from server time. The recipes below are self-contained TypeScript where possible, followed by production adaptations and review criteria.

TL;DR

// Fixed calendar state, timers continue normally.
await page.clock.setFixedTime(new Date('2026-07-13T12:00:00Z'));

// Controlled passage, install before page code starts.
await page.clock.install({ time: new Date('2026-07-13T12:00:00Z') });
await page.clock.runFor(5_000);      // Fire callbacks throughout five seconds.
await page.clock.fastForward('05:00'); // Jump five minutes, overdue timers fire once.
Scenario Clock pattern Key evidence
Date-based label setFixedTime() Exact business state or label
Countdown install() plus runFor() Intermediate and zero states
Inactivity install() plus fastForward() Session termination and controls
Debounced search install() plus runFor() One request and rendered result
Wake after long gap fastForward() Recovery behavior
Clock correction or DST shift setSystemTime() Recomputed local state, no assumed timer firing

1. Build a deterministic example harness

The examples use page.setContent() or request routing so they run without an external server. In an application suite, keep the same Clock order but replace the synthetic markup with page.goto() and real user-facing locators.

import { test, expect } from '@playwright/test';

const instant = (value: string) => new Date(value);

test('clock harness starts from an explicit instant', async ({ page }) => {
  await page.clock.install({ time: instant('2026-07-13T09:00:00Z') });
  await page.setContent(`<output data-testid="iso"></output>
    <script>
      document.querySelector('[data-testid="iso"]').textContent =
        new Date().toISOString();
    </script>`);

  await expect(page.getByTestId('iso'))
    .toHaveText('2026-07-13T09:00:00.000Z');
});

This assertion is a setup smoke check, not a complete product test. Use it only when diagnosing the harness. Real scenarios should assert pricing, eligibility, state transitions, user feedback, or requests.

Always include an offset in test instants. The Z means UTC. An ISO string without an offset can map to different instants under different environment timezones. If local display matters, configure a named timezoneId and a locale on the browser context in addition to selecting the instant.

One test should use either fixed-date mode or installed-clock mode. Do not set a fixed time and then install later. Installation must precede page code that creates timeouts, intervals, animation frames, or related callbacks.

2. Playwright mock date and time examples for deadlines

A deadline rule needs at least three cases: immediately before, exactly at, and immediately after the boundary. Keep the expected business outcome explicit rather than deriving it with the same comparison used by production.

import { test, expect } from '@playwright/test';

const cases = [
  { name: 'before deadline', now: '2026-07-13T11:59:59Z', expected: 'Apply now' },
  { name: 'at deadline', now: '2026-07-13T12:00:00Z', expected: 'Applications closed' },
  { name: 'after deadline', now: '2026-07-13T12:00:01Z', expected: 'Applications closed' },
];

for (const item of cases) {
  test(item.name, async ({ page }) => {
    await page.clock.setFixedTime(new Date(item.now));
    await page.setContent(`
      <p role="status"></p>
      <script>
        const closesAt = Date.parse('2026-07-13T12:00:00Z');
        document.querySelector('[role="status"]').textContent =
          Date.now() < closesAt ? 'Apply now' : 'Applications closed';
      </script>
    `);

    await expect(page.getByRole('status')).toHaveText(item.expected);
  });
}

Separate tests give every boundary a clear report entry and let later cases run if one fails. The expected strings are independent facts. Computing expected with Date.parse(item.now) < deadline inside the test would copy the production decision and reduce defect detection.

For a real flow, assert both message and capability. Before the deadline, the application button should be enabled. At and after it, the button should be disabled or absent according to the requirement. A label alone can be correct while the protected action remains available.

3. Test a countdown with runFor

Countdowns need advancing Date.now() and repeated timer callbacks. Install the Clock, render the component, and use runFor() so intermediate callbacks execute.

import { test, expect } from '@playwright/test';

test('counts down and enables retry at zero', async ({ page }) => {
  await page.clock.install({ time: new Date('2026-07-13T10:00:00Z') });
  await page.setContent(`
    <p data-testid="remaining"></p>
    <button disabled>Try again</button>
    <script>
      const endsAt = Date.now() + 3_000;
      const render = () => {
        const seconds = Math.max(0, Math.ceil((endsAt - Date.now()) / 1000));
        document.querySelector('[data-testid="remaining"]').textContent =
          String(seconds);
        if (seconds === 0) {
          document.querySelector('button').disabled = false;
          return;
        }
        setTimeout(render, 1000);
      };
      render();
    </script>
  `);

  const remaining = page.getByTestId('remaining');
  const retry = page.getByRole('button', { name: 'Try again' });
  await expect(remaining).toHaveText('3');
  await expect(retry).toBeDisabled();

  await page.clock.runFor(2_000);
  await expect(remaining).toHaveText('1');

  await page.clock.runFor(1_000);
  await expect(remaining).toHaveText('0');
  await expect(retry).toBeEnabled();
});

Assert a representative intermediate state and the terminal behavior. Testing every tick in a sixty-second countdown creates noise unless skipped or duplicated values are known risks.

setFixedTime() would be wrong here because timer callbacks could run while Date.now() remained unchanged. A real waitForTimeout(3000) would slow the test and still depend on scheduler timing. Installed Clock matches the design and completes deterministically.

4. Model inactivity and laptop sleep with fastForward

Inactivity behavior often cares about elapsed wall time after a gap, not every one-second check. fastForward() jumps ahead and fires overdue timers at most once, similar to reopening a sleeping laptop.

test('locks an unattended workspace', async ({ page }) => {
  await page.clock.install({ time: new Date('2026-07-13T10:00:00Z') });
  await page.setContent(`
    <main>
      <p role="status">Workspace unlocked</p>
      <button>Continue editing</button>
    </main>
    <script>
      const lockAt = Date.now() + 15 * 60_000;
      setInterval(() => {
        if (Date.now() >= lockAt) {
          document.querySelector('[role="status"]').textContent = 'Workspace locked';
          document.querySelector('button').disabled = true;
        }
      }, 1000);
    </script>
  `);

  await page.clock.fastForward('15:00');

  await expect(page.getByRole('status')).toHaveText('Workspace locked');
  await expect(page.getByRole('button', { name: 'Continue editing' }))
    .toBeDisabled();
});

Add a separate reset test when user interaction extends the deadline. Trigger the real interaction, advance to just before the renewed boundary, prove the session remains active, then cross the boundary. Avoid directly mutating a JavaScript deadline through evaluate(), which bypasses the product's event wiring.

Use runFor() instead if the contract requires fifteen interval calls or a visible per-second audit. The distinction between run and jump is a behavioral choice, not a performance trick.

5. Verify debounce and polling without fixed sleeps

Debounced search should wait for quiet input, make the expected request, and render the result. Install Clock before the component creates its timeout, route the API, and advance only the debounce duration.

import { test, expect } from '@playwright/test';

test('sends one search after the debounce window', async ({ page }) => {
  await page.clock.install();
  let calls = 0;

  await page.route('**/api/search?query=*', async route => {
    calls += 1;
    await route.fulfill({ json: { names: ['Playwright Engineer'] } });
  });

  await page.route('https://search.example.test/', route => route.fulfill({
    contentType: 'text/html',
    body: `
      <label>Search <input></label><ul aria-label="Results"></ul>
      <script>
        let timer;
        const input = document.querySelector('input');
        input.addEventListener('input', () => {
          clearTimeout(timer);
          timer = setTimeout(async () => {
            const response = await fetch('/api/search?query=' + input.value);
            const data = await response.json();
            document.querySelector('ul').innerHTML =
              data.names.map(name => '<li>' + name + '</li>').join('');
          }, 300);
        });
      </script>
    `,
  }));

  await page.goto('https://search.example.test/');
  await page.getByLabel('Search').pressSequentially('playwright');
  expect(calls).toBe(0);

  await page.clock.runFor(300);
  await expect(page.getByRole('listitem')).toHaveText('Playwright Engineer');
  expect(calls).toBe(1);
});

For polling, route sequential responses, advance one polling interval at a time, and assert the visible state after each meaningful response. Clock fires timers but does not force pending fetch promises to resolve. Let the web-first UI assertion observe response processing before advancing again.

The Playwright API mocking guide is a useful companion for deterministic route design.

6. Cross midnight with pauseAt

Calendar features often schedule a refresh but compute state from the local day. Install shortly before the target, navigate while time flows, then pause at the exact instant where you want to inspect the page.

import { test, expect } from '@playwright/test';

test.use({ timezoneId: 'Asia/Kolkata', locale: 'en-IN' });

test('moves to the next local business date', async ({ page }) => {
  await page.clock.install({ time: new Date('2026-07-13T18:29:50Z') });
  await page.setContent(`
    <p data-testid="day"></p>
    <script>
      const render = () => {
        document.querySelector('[data-testid="day"]').textContent =
          new Intl.DateTimeFormat('en-CA').format(new Date());
      };
      render();
      setInterval(render, 1000);
    </script>
  `);

  await expect(page.getByTestId('day')).toHaveText('2026-07-13');
  await page.clock.pauseAt(new Date('2026-07-13T18:30:00Z'));
  await expect(page.getByTestId('day')).toHaveText('2026-07-14');
});

At UTC+05:30, 18:30Z is local midnight. The explicit context timezone makes the boundary portable. In a production suite, prefer product-controlled machine-readable attributes or normalized labels if the browser's locale formatter output varies in punctuation.

pauseAt() jumps and pauses. For best results, install at a slightly earlier instant so initialization callbacks run normally, then navigate, then pause. If the component needs multiple callbacks through the last ten seconds, use runFor(10_000) instead.

7. Exercise wall-clock corrections with setSystemTime

setSystemTime() changes the system time without firing timers. It is useful for testing how an application reacts when the wall clock changes, such as a daylight-saving adjustment or manual correction. Install the Clock first, because this is an advanced installed-clock scenario.

test('recalculates a local schedule after a clock shift', async ({ page }) => {
  await page.clock.install({ time: new Date('2026-11-01T05:30:00Z') });
  await page.setContent(`
    <button>Refresh schedule</button>
    <output data-testid="hour"></output>
    <script>
      const render = () => {
        document.querySelector('output').textContent = String(new Date().getUTCHours());
      };
      document.querySelector('button').addEventListener('click', render);
      render();
    </script>
  `);

  await expect(page.getByTestId('hour')).toHaveText('5');
  await page.clock.setSystemTime(new Date('2026-11-01T06:30:00Z'));

  // setSystemTime does not promise to trigger the application's timers.
  await expect(page.getByTestId('hour')).toHaveText('5');
  await page.getByRole('button', { name: 'Refresh schedule' }).click();
  await expect(page.getByTestId('hour')).toHaveText('6');
});

For a meaningful timezone-specific assertion, configure timezoneId for the target region and assert the documented local label. The example intentionally shows the interaction pattern: change system time, then trigger the product's supported refresh because timers are not fired by setSystemTime().

Do not use this method merely to advance a countdown. Use runFor() or fastForward() for elapsed time. The Playwright timezone testing guide can help structure regional boundary coverage.

8. Combine Clock with route data for expiry

Client-side expiry decisions usually combine current browser time with an expiry supplied by an API. Control both inputs and distinguish the UI contract from server enforcement.

test('disables an expired password-reset link', async ({ page }) => {
  await page.clock.setFixedTime(new Date('2026-07-13T12:00:00Z'));

  await page.route('**/api/reset-token', route => route.fulfill({
    json: {
      status: 'issued',
      expiresAt: '2026-07-13T11:59:59Z',
    },
  }));

  await page.goto('/reset-password?token=test-token');

  await expect(page.getByRole('alert')).toHaveText('This link has expired');
  await expect(page.getByLabel('New password')).toBeDisabled();
  await expect(page.getByRole('button', { name: 'Reset password' }))
    .toBeDisabled();
});

Do not put real credentials or live reset tokens in fixtures. Mocked route data should use obviously synthetic values. Verify backend rejection separately through an API or integration test where the server's clock and signature validation are authoritative.

When the page opens a popup, iframe, or another tab in the same BrowserContext, the installed Clock applies there too. Route at context level when the first navigation request of a popup must be controlled. For ordinary page API calls, page-level routing may be enough.

9. Create boundary-focused data-driven suites

A date matrix should represent distinct equivalence classes, not every day on a calendar. Name cases by business meaning and keep expected outcomes literal.

Feature Boundary cases Avoid
Coupon expiry One second before, exact expiry, one second after Ten arbitrary dates
Booking window Last unavailable day, first available day Deriving expected with production helper
Daily quota Last action before local midnight, first after UTC-only assumptions
Session timeout Interaction before threshold, exact idle threshold Real multi-minute waits
DST schedule Valid instants before and after transition Constructing nonexistent local time casually

Use a data table only when interaction shape is the same. Split scenarios when setup, behavior, or expected evidence differs. A countdown tick and a laptop-sleep recovery may use the same component but require runFor() and fastForward() respectively, so separate tests communicate more than a generic parameter.

Keep exhaustive pure date arithmetic in unit tests. Browser tests should prove that context timezone, Clock, rendering, storage, user events, and network data are wired correctly. This test pyramid boundary prevents a slow matrix from growing without additional confidence.

When a failure report says only case 4, rename the test data. Titles such as closes application at exact deadline make triage possible without reopening the source. Also record the explicit instant in the data object so the evidence remains reproducible.

10. Review playwright mock date and time examples before merge

First, identify every time authority. Browser Date may control a label, while the backend controls authorization and a database default controls audit timestamps. Ensure the test's claim matches the clock it actually controls.

Second, check order and method semantics. Installed Clock must precede application timers. Fixed time is for a stable instant, runFor() for continuous callbacks, fastForward() for a jump, pauseAt() for a paused target, and setSystemTime() for a wall-clock change without timer firing. A method chosen only because it made the test pass deserves review.

Third, inspect the boundary and evidence. Require before, exact, and after cases for comparisons whose equality rule matters. Assert both presentation and capability for expiry, such as an error plus a disabled action. Make timezone, locale, and ISO offset explicit when local dates are involved.

Finally, remove fixed sleeps, production-derived expectations, broad date regexes, secret fixtures, and shared-context leakage. Run the scenario across the browser projects that matter and retain traces on failure. A clean time test should be fast, deterministic, and honest about which layer it verifies.

11. Keep Clock examples reliable in CI and browser projects

Run time-sensitive browser behavior in every engine your product supports, but do not assume that one exact localized string is portable without a controlled context. Put stable timezone and locale choices in a project or a test-level use block, and let each Playwright project create its own isolated BrowserContext.

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    trace: 'retain-on-failure',
    locale: 'en-US',
    timezoneId: 'America/Chicago',
  },
  projects: [
    { name: 'chromium', use: { browserName: 'chromium' } },
    { name: 'firefox', use: { browserName: 'firefox' } },
    { name: 'webkit', use: { browserName: 'webkit' } },
  ],
});

The configuration controls the browser context, not the operating system globally. This makes parallel workers safe as long as tests do not share external records. Give every test unique server data or mock the relevant endpoint, especially when expiry state can be mutated by another worker.

When a Clock example fails in CI, retain the trace and inspect the order of installation, navigation, advancement, network completion, and assertion. During focused diagnosis, capture new Date().toISOString() inside the page and compare it with the test fixture. Also inspect the context timezone and the original expiry value. Remove temporary logs after the issue is understood, since time-related payloads can still reveal customer data in real suites.

Avoid compensating for a failing transition by increasing the overall test timeout. Clock advancement itself should be quick. A long wait usually means the application used an uncontrolled scheduler, waited on a network dependency, or never received the expected event. Fix that boundary or state explicitly that the scenario cannot be accelerated at the browser layer.

Interview Questions and Answers

Q: Why is setFixedTime() unsuitable for some countdowns?

It fixes values returned by Date.now() and new Date() while timers continue. A countdown based on the difference between an end instant and Date.now() may never decrease. Installing Clock and using runFor() advances the time value and callbacks together.

Q: How would you test an exact expiry boundary?

I create independent cases immediately before, exactly at, and immediately after the documented expiry. I keep expected eligibility literal, control API expiry data separately, and assert both the message and whether the action can execute.

Q: What does fastForward model?

It models a jump such as closing and reopening a laptop. Time advances, but due timers fire at most once. That makes it appropriate for inactivity wake-up behavior and inappropriate when every interval callback is part of the requirement.

Q: How do you make a local-midnight test portable?

I configure a named IANA timezone, use an explicit UTC instant with offset, and choose instants on both sides of that timezone's midnight. I also control locale or assert a normalized product value so CI formatting cannot change the result.

Q: Can Clock accelerate an API response?

Clock triggers supported browser scheduling functions, but it does not make a remote service respond. I mock or control network data where appropriate and wait for the UI to process a response before advancing another interval.

Q: Why should expected date behavior not be calculated with the production helper?

That duplicates the implementation under test and can make the same defect appear in actual and expected values. I use explicit business facts in the test data and reserve helper-level exhaustive calculations for independent unit tests.

Q: What is the role of test isolation in Clock tests?

Clock state applies across a BrowserContext. Playwright Test's fresh context per test prevents leakage naturally. Reused custom contexts can carry mocked time into unrelated tests and should be avoided or closed explicitly.

Common Mistakes

  • Using a real sleep to wait for a timer that Clock can drive deterministically.
  • Installing Clock after the page has created native timers.
  • Using fixed time for behavior that needs elapsed Date.now().
  • Using runFor() to model laptop sleep or fastForward() to prove every tick.
  • Omitting timezone offsets from ISO test values.
  • Treating locale, timezone, and instant as one input.
  • Advancing Clock again before a mocked network response is rendered.
  • Claiming backend expiry coverage from a browser-only Clock test.
  • Calculating expected outcomes with the same date rule as production.
  • Testing only a label while the forbidden action remains enabled.
  • Reusing a BrowserContext and leaking controlled time.
  • Building a large random date matrix that misses equality boundaries.

Conclusion

Strong playwright mock date and time examples connect a specific Clock method to a specific user risk. Fix the instant for date-based rendering, run through callbacks for countdowns and debounce, jump for inactivity, pause at calendar boundaries, and change system time only when wall-clock correction is the behavior.

Choose one production time rule, write explicit before, boundary, and after facts, then assert the business outcome at the browser layer. That pattern produces fast evidence without pretending the browser Clock controls systems outside its context.

Interview Questions and Answers

Give a strategy for testing a time-based deadline.

I define explicit cases immediately before, at, and after the boundary. I control the browser instant, independently seed the deadline, and assert both status and capability. I also identify whether the server needs a separate enforcement test.

How would you test a three-second countdown efficiently?

I install Clock before the component starts, assert three seconds and the disabled action, run for two seconds, check a representative intermediate state, then run the final second. I assert zero and the enabled terminal action without a real sleep.

When is fastForward behavior preferable in a test?

It is preferable when the real event is a long gap or device sleep and overdue callbacks should not all replay. Session inactivity and wake-up recovery are good examples. It is not a substitute for continuous timer passage.

How do you combine network mocking with Clock?

I register routes before navigation, install Clock before timers, and supply explicit server data such as an expiry instant. After advancing time, I wait for the UI to process any network response before the next advancement. I assert both protocol behavior and user outcome when each is a requirement.

How do you test daylight-saving behavior responsibly?

I configure the named timezone, use real unambiguous instants on each side of the transition, and state whether the app reacts to elapsed time or a wall-clock shift. I use `setSystemTime()` only when a time correction without timer firing matches the requirement.

Why are literal expected outcomes valuable in date test data?

They remain independent from the implementation's comparison or formatting code. If the test derives expected values with the same algorithm, one defect can confirm itself. Literal business outcomes also make review and reports clearer.

What is your review checklist for a mocked-time test?

I check time authority, installation order, Clock method semantics, explicit offsets, timezone and locale, equality boundaries, and user-visible evidence. I reject fixed sleeps, context sharing, production-derived expectations, and backend claims unsupported by browser control.

Frequently Asked Questions

What is a runnable Playwright fixed date example?

Call `page.clock.setFixedTime()` with an explicit ISO instant before `page.goto()` or `page.setContent()`, then assert the date-dependent product state. Use a fresh BrowserContext per test so the controlled date cannot leak.

How do I test a countdown in Playwright?

Install Clock before the countdown starts, assert its initial state, call `page.clock.runFor()` for representative durations, and assert an intermediate and terminal state. This advances Date and supported timer callbacks coherently.

How do I test debounce without waitForTimeout?

Install Clock before page startup, perform the real typing interaction, confirm no early request if that matters, then use `runFor()` for the debounce duration. Assert both request behavior and the rendered UI result.

Should I use runFor or fastForward for session inactivity?

Use `fastForward()` when modeling a user who leaves and returns after a gap. Use `runFor()` only when every scheduled check or intermediate countdown update is relevant to the requirement.

How do I test a deadline at local midnight?

Set a named `timezoneId`, select explicit instants immediately before and after local midnight, and control locale if text formatting matters. Do not assume UTC midnight equals the business boundary.

Can Playwright Clock mock JWT expiry?

It can control client-side current time used to interpret expiry. Seed or mock the token or API expiry independently, and use a server-side test for actual backend rejection because Clock does not affect the server.

What should a Clock test assert besides the displayed time?

Assert the business effect, such as enabled or disabled actions, session status, request cadence, eligibility, refreshed content, or an expiry message. A timestamp-only check mainly verifies the harness.

Related Guides