Resource library

QA How-To

How to Use Playwright mock date and time (2026)

Learn playwright mock date and time with Clock APIs for fixed dates, timers, timezones, expiry, polling, deterministic tests, and CI debugging in 2026.

24 min read | 2,568 words

TL;DR

Use `page.clock.setFixedTime()` for a stable browser date. For countdowns, polling, inactivity, or time travel, call `page.clock.install()` before navigation and advance time with `runFor()`, `fastForward()`, or `pauseAt()`.

Key Takeaways

  • Use setFixedTime when only Date.now and new Date need one stable instant.
  • Install the Clock before navigation when application timers must be controlled.
  • Use runFor for every scheduled callback and fastForward for sleep-like jumps.
  • Configure instant, timezone, and locale as separate test inputs.
  • Remember that browser Clock does not change server, database, or runner time.
  • Test before, exact, and after cases at business time boundaries.
  • Assert user-visible outcomes instead of merely confirming the mocked timestamp.

Playwright mock date and time tests should control the browser's clock before the application starts, then assert the user-visible behavior that depends on that clock. For a fixed Date.now() or new Date(), use page.clock.setFixedTime(). For timers, countdowns, polling, inactivity, or deliberate time travel, install the Clock and advance it with runFor(), fastForward(), or pauseAt().

The Clock API is a better default than replacing window.Date with a homegrown script because it coordinates the browser time functions that modern applications use. This guide explains the 2026 API, correct setup order, timezone boundaries, real runnable TypeScript tests, and the design choices that keep time tests deterministic without hiding production defects.

TL;DR

Requirement Recommended Clock method What happens
Render the app at one known instant setFixedTime(time) Date.now() and new Date() stay fixed, timers keep running
Advance through every scheduled callback install() then runFor(duration) Time advances and due time callbacks fire
Simulate laptop sleep or a large jump install() then fastForward(duration) Time jumps and due timers fire at most once
Load normally, then stop at an instant install({ time }) then pauseAt(time) Clock jumps to the instant and pauses
Continue ordinary time after a pause resume() Timers and time flow resume
Change wall clock without firing timers setSystemTime(time) System time changes, scheduled timers are not triggered
import { test, expect } from '@playwright/test';

test('renders a known business date', async ({ page }) => {
  await page.clock.setFixedTime(new Date('2026-07-13T09:30:00Z'));
  await page.goto('/dashboard');
  await expect(page.getByTestId('business-date')).toHaveText('Jul 13, 2026');
});

Use one clear clock strategy per test. Install the clock before application code creates timers, and configure locale or timezone separately when displayed formatting matters.

1. Understand what Playwright Clock controls

Playwright Clock replaces browser globals associated with time for the entire BrowserContext. That means all pages and iframes in the context share the controlled clock. The covered surface includes Date, timeout and interval functions, animation frame scheduling, idle callbacks, performance, and event timestamps. This breadth matters because a partial fake can make Date.now() disagree with timers or performance measurements.

The Clock changes browser-side time. It does not change the Node.js process clock, a database server, an API server, email timestamps, or third-party systems. Code in the test runner that calls new Date() still sees the runner's real time unless you independently inject data there. A complete scenario must decide which time authority owns each result.

await page.clock.setFixedTime(new Date('2026-01-15T12:00:00Z'));
await page.setContent(`<output id="now"></output>
  <script>document.querySelector('#now').textContent = Date.now();</script>`);

await expect(page.locator('output')).toHaveText(
  String(Date.parse('2026-01-15T12:00:00Z'))
);

Clock state belongs to the isolated browser context created for the test. Playwright Test normally gives each test a fresh context, which prevents a mocked clock from leaking into the next test. If a custom fixture reuses a context, clock state becomes another reason to reconsider that reuse.

Time control should support a business assertion. Testing only that Date.now() equals the value you configured proves the test setup, not the product. Prefer expiry labels, countdown transitions, session behavior, scheduled refreshes, and date-sensitive eligibility outcomes.

2. Set up playwright mock date and time correctly

Start a TypeScript project with the Playwright initializer, then put time scenarios in ordinary *.spec.ts files.

npm init playwright@latest
npx playwright test tests/time.spec.ts

The most important setup rule is order. If you use clock.install(), call it before the page executes time-related application code. Installing after an interval, timeout, or animation loop has been created replaces native functions too late and can produce undefined behavior.

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

test('starts the application with an installed clock', async ({ page }) => {
  await page.clock.install({
    time: new Date('2026-07-13T08:00:00Z'),
  });

  await page.goto('/session');
  await expect(page.getByRole('heading', { name: 'Your session' }))
    .toBeVisible();
});

Do not call setFixedTime() and later call install() in the same test as if install were an upgrade. Choose the fixed-date path or install first and use advanced controls. If shared setup needs an installed clock, create a fixture or beforeEach hook that performs the installation before navigation.

Store test instants as ISO strings with an explicit Z or numeric offset. A string such as 2026-07-13T09:00:00 has no offset and can be interpreted in an environment-dependent timezone. Explicit instants make local and CI runs agree.

3. Use setFixedTime for simple date-dependent pages

setFixedTime() makes Date.now() and zero-argument new Date() return the supplied value while browser timers continue to run. It is the recommended simple choice when the application reads the current date but the test does not need time to progress consistently with timers.

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

test('shows a campaign before its deadline', async ({ page }) => {
  await page.clock.setFixedTime(new Date('2026-11-27T15:00:00Z'));

  await page.setContent(`
    <p data-testid="campaign"></p>
    <script>
      const deadline = Date.parse('2026-11-28T00:00:00Z');
      document.querySelector('[data-testid="campaign"]').textContent =
        Date.now() < deadline ? 'Offer available' : 'Offer ended';
    </script>
  `);

  await expect(page.getByTestId('campaign')).toHaveText('Offer available');
});

This mode has a deliberate asymmetry: real timers keep firing, but calls to Date.now() stay fixed. That is perfect for a date banner refreshed by a harmless interval, but wrong for a countdown that computes remaining time from Date.now(). The countdown would keep seeing the same instant.

You can call setFixedTime() again to change the fixed value. An application that re-renders on a timer can then observe the new date. Still, do not use it to model a continuous five-minute passage. Install the clock and advance it so timer callbacks and time values move coherently.

4. Install the clock for timers and continuous passage

clock.install({ time }) installs fake implementations and initializes the controlled clock. After installation, use the advanced methods to drive time. Install before navigation whenever the page may create timers during startup.

The following self-contained test proves a delayed state without waiting ten real seconds:

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

test('reveals a resend action after ten seconds', async ({ page }) => {
  await page.clock.install({
    time: new Date('2026-07-13T10:00:00Z'),
  });

  await page.setContent(`
    <button disabled>Resend code</button>
    <script>
      setTimeout(() => {
        document.querySelector('button').disabled = false;
      }, 10_000);
    </script>
  `);

  const resend = page.getByRole('button', { name: 'Resend code' });
  await expect(resend).toBeDisabled();

  await page.clock.runFor(10_000);

  await expect(resend).toBeEnabled();
});

The test checks both sides of the transition. runFor() advances the clock and fires all relevant callbacks along the path. No fixed test sleep is needed, and the test finishes quickly even though it covers a ten-second product rule.

Installation is context-wide. A popup opened later shares the clock, as do iframes. This is useful for coherent multi-page workflows but means a time change intended for one page affects every page in the same context.

5. Choose runFor, fastForward, pauseAt, resume, or setSystemTime

The advanced methods model different kinds of passage. runFor() advances time while firing all time-related callbacks. Use it for countdown ticks, repeated polling, animation frames, and debounced work where intermediate callbacks matter.

fastForward() jumps by milliseconds or a supported human-readable duration such as '05:00'. Due timers fire at most once, which resembles closing a laptop and reopening it later. Use it for inactivity and wake-up behavior, not for asserting every one-second tick.

pauseAt(instant) jumps to a supplied instant and pauses. The recommended setup is to install at an earlier time, allow page startup to run normally, navigate, and then pause at the target. resume() restarts normal flow.

await page.clock.install({ time: new Date('2026-07-13T08:00:00Z') });
await page.goto('/operations');

await page.clock.pauseAt(new Date('2026-07-13T09:00:00Z'));
await expect(page.getByTestId('shift')).toHaveText('Morning shift');

await page.clock.runFor('01:00');
await page.clock.resume();

setSystemTime() changes the system time without firing timers. It is an advanced tool for behavior after wall-clock shifts, including daylight-saving transitions. It does not simulate elapsed time. Use it only when that distinction is the requirement.

For related asynchronous patterns, review async and await in QA tests. Every Clock method returns a Promise, so await it before asserting the new state.

6. Test countdowns, inactivity, and scheduled polling

Time-driven components often mix Date.now() with recursive timeouts or intervals. That is exactly where an installed clock is stronger than a fixed Date.

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

test('logs out after five minutes of inactivity', async ({ page }) => {
  await page.clock.install({
    time: new Date('2026-07-13T10:00:00Z'),
  });

  await page.setContent(`
    <p role="status">Signed in</p>
    <script>
      const expiresAt = Date.now() + 5 * 60_000;
      const check = () => {
        if (Date.now() >= expiresAt) {
          document.querySelector('[role="status"]').textContent = 'Signed out';
          return;
        }
        setTimeout(check, 1000);
      };
      check();
    </script>
  `);

  await expect(page.getByRole('status')).toHaveText('Signed in');
  await page.clock.fastForward('05:00');
  await expect(page.getByRole('status')).toHaveText('Signed out');
});

For a visible countdown that must show every intermediate second, use runFor() in deliberate increments and assert representative boundaries. Do not mirror every implementation tick unless each tick is a product requirement.

For scheduled API polling, combine Clock with controlled network responses. Route the endpoint before navigation, count requests, advance time, and assert the UI outcome as well as request count when cadence is the requirement. Clock does not speed an actual backend or automatically complete pending network requests. Keep the mock deterministic and avoid advancing time again until the expected response has been processed.

7. Separate instants, locale, and timezone

An instant, a timezone, and a locale are three different inputs. The instant answers when. The timezone determines the local calendar and clock representation. The locale determines formatting conventions such as month names and ordering. Mocking one does not automatically configure the others.

Set stable context options through Playwright Test when formatting matters:

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

test.use({
  locale: 'en-US',
  timezoneId: 'America/New_York',
});

test('formats a fixed instant in New York', async ({ page }) => {
  await page.clock.setFixedTime(new Date('2026-07-13T13:30:00Z'));
  await page.setContent(`
    <p data-testid="local"></p>
    <script>
      document.querySelector('[data-testid="local"]').textContent =
        new Intl.DateTimeFormat(undefined, {
          dateStyle: 'medium', timeStyle: 'short'
        }).format(new Date());
    </script>
  `);

  await expect(page.getByTestId('local')).toContainText('Jul 13, 2026');
});

Avoid brittle punctuation assertions unless exact formatting is part of the contract. Browsers can legitimately render locale-specific spaces or separators. Assert semantic pieces or a product-normalized string where appropriate.

Boundary tests should choose instants around midnight in the configured zone, not merely around midnight UTC. Daylight-saving tests should use named IANA timezone IDs and explicit instants on each side of the transition. The Playwright device emulation guide covers context-level locale and timezone configuration in broader browser profiles.

8. Coordinate browser time with API and token expiry

Many end-to-end scenarios fail because the UI clock and backend clock disagree. A browser-side JWT decoder may think a token is expired while the server still accepts it, or the opposite. Clock cannot move a remote service's time.

Choose a boundary strategy. For a pure UI test, mock the API response and let the browser Clock drive client decisions. For an integration test, seed records with explicit expiry instants accepted by a controllable test backend. For a full environment test, use a supported server time-injection mechanism rather than changing machine time globally.

test('shows an expired invitation from controlled data', async ({ page }) => {
  await page.clock.setFixedTime(new Date('2026-07-13T12:00:00Z'));

  await page.route('**/api/invitations/abc', route => route.fulfill({
    json: { id: 'abc', expiresAt: '2026-07-13T11:59:59Z' },
  }));

  await page.goto('/invitations/abc');
  await expect(page.getByRole('alert')).toHaveText('Invitation expired');
  await expect(page.getByRole('button', { name: 'Accept invitation' }))
    .toBeDisabled();
});

This test controls both facts the UI needs: current browser time and expiry data. It does not claim that the production backend enforces expiry. That deserves an API or integration test using the server's time authority.

Never generate expected values by calling the same UI date formatter inside page.evaluate(). Derive expectations from independent fixture facts so an application formatting defect cannot confirm itself.

9. Design reusable clock fixtures without hidden state

A small fixture can standardize ISO instants and ensure installation happens before navigation. Keep the choice visible in test code so readers know whether time is fixed or advancing.

import { test as base, expect, type Page } from '@playwright/test';

type TimeFixtures = {
  installClock: (iso: string) => Promise<void>;
};

const test = base.extend<TimeFixtures>({
  installClock: async ({ page }, use) => {
    await use(async (iso: string) => {
      await page.clock.install({ time: new Date(iso) });
    });
  },
});

test('expires a draft after thirty minutes', async ({ page, installClock }) => {
  await installClock('2026-07-13T09:00:00Z');
  await page.goto('/drafts/42');

  await page.clock.runFor('30:00');
  await expect(page.getByRole('status')).toHaveText('Draft expired');
});

Do not put navigation inside a universal time fixture unless every test opens the same page. Hidden navigation and clock mode make failures difficult to reason about. A helper that accepts arbitrary local date strings is also risky because missing offsets reintroduce environment dependence.

Playwright Test isolation should remain the cleanup mechanism. Each test receives a new context, so there is no need for a fictional clock.uninstall() method. Do not invent one. If your suite manually creates or reuses contexts, close them explicitly and keep time-controlled tests isolated.

10. Debug playwright mock date and time failures

When a time test fails only in CI, inspect four layers: the configured instant, timezone and locale, clock installation order, and the actual time authority used by the feature. Logging a safe ISO value from the browser can confirm setup during diagnosis.

const observed = await page.evaluate(() => new Date().toISOString());
console.log('browser instant:', observed);

Remove diagnostic logging when it is no longer useful. Trace viewer can reveal whether navigation occurred before installation, whether the relevant script started a timer, and when assertions ran. Browser console errors may show an invalid date or a formatter exception.

If runFor() does not cause the expected state, determine whether the application uses browser timers, a Web Worker, a server push channel, or a network response. Clock controls the page's supported time globals, not every external scheduler. If a timer was created before install(), fix setup order rather than adding more advancement.

If exact formatted text differs, compare the browser context's timezone and locale, the ISO offset, and Unicode whitespace. Do not change an exact business assertion into a broad regex until you understand the allowed format.

The Playwright timeout troubleshooting guide can help distinguish a missing time transition from an unrelated navigation or actionability timeout.

11. Build a focused time-risk test matrix

Do not test every date. Select boundaries where behavior changes: one instant before a deadline, the exact deadline, and one after; the last and first local minute of a day; the transition into or out of daylight saving when the product supports that zone; and one normal control case.

Risk Minimum useful cases Primary assertion
Expiry rule Before, exact boundary, after Eligibility and enabled state
Countdown Start, representative tick, zero Display and terminal action
Inactivity Recent interaction, threshold reached Session state
Scheduled refresh Before interval, first interval, later interval Data and request cadence
Local business day Just before and after local midnight Date bucket or availability
DST-sensitive schedule Instants on both sides of transition Displayed local time and rule outcome

Keep most combinations in fast unit tests for date calculation functions. Use Playwright for browser wiring, rendered formatting, timer integration, user actions, storage, and navigation. A small risk-based matrix produces more signal than dozens of nearly identical end-to-end dates.

Document which clock owns the decision. If the server owns subscription expiry, a browser-only mock is not enough. If the browser owns a resend countdown, the Clock API is exactly the right layer. This separation makes failures actionable and prevents inflated coverage claims.

Interview Questions and Answers

Q: What is the simplest way to mock the current date in Playwright?

Use await page.clock.setFixedTime(new Date('...')) before navigation. It fixes Date.now() and zero-argument new Date() while ordinary timers keep running. I use it when continuous passage is not part of the requirement.

Q: When should you use clock.install() instead?

Use it when timers and time must advance coherently, such as countdowns, polling, debouncing, animation, or inactivity. Install before application code creates any timer, then control passage with runFor(), fastForward(), pauseAt(), and resume().

Q: What is the difference between runFor() and fastForward()?

runFor() advances through time and fires all supported time callbacks. fastForward() models a jump, with due timers firing at most once. I choose based on whether intermediate ticks are behavior or whether the product should wake after an elapsed gap.

Q: Does Playwright Clock change backend time?

No. It controls browser-side time in the BrowserContext. Server, database, test runner, and third-party clocks need controlled data or supported time injection at their own layers.

Q: How do timezone and fixed time interact?

Fixed time supplies an instant. The context timezoneId determines its local representation, and locale determines formatting conventions. I configure all three explicitly when asserting local date or time text.

Q: Why must installation happen before navigation?

Applications often create intervals, timeouts, and animation callbacks during startup. Installing afterward replaces globals after native callbacks already exist and can cause inconsistent or undefined behavior. Early installation gives the entire page lifecycle one clock.

Q: How do you test an expiry boundary without duplicating production logic?

I choose explicit instants before, at, and after the documented boundary and seed independent expiry data. I assert user-visible eligibility and actions. I do not compute the expected result using the application's own date helper.

Common Mistakes

  • Installing the clock after navigation or after application timers have started.
  • Calling setFixedTime() and then trying to switch to install() in the same test.
  • Using offset-free date strings that change meaning across environments.
  • Assuming browser Clock changes the backend, database, or Node.js runner time.
  • Using fixed time for a countdown that needs Date.now() to advance.
  • Using fastForward() when every interval callback must be observed.
  • Asserting localized punctuation without controlling locale and timezone.
  • Testing only the mocked timestamp instead of the product behavior.
  • Advancing time while a required network response is still unresolved.
  • Sharing a BrowserContext and leaking clock state between tests.
  • Inventing an unsupported clock.uninstall() cleanup method.
  • Covering many ordinary dates while missing the exact business boundary.

Conclusion

Reliable Playwright mock date and time coverage starts by choosing the right clock model. Use setFixedTime() for a known instant, or install the Clock before page startup when timers and passage matter. Then configure timezone and locale deliberately, control data from other time authorities, and assert real business outcomes.

Begin with one risky rule such as token expiry, inactivity, or local midnight. Add the before, boundary, and after cases, make each instant explicit, and let Playwright's Clock replace slow waits with deterministic browser behavior.

Interview Questions and Answers

Describe the Playwright Clock API and its purpose.

The Clock API controls browser-side Date and supported scheduling functions for a BrowserContext. `setFixedTime()` handles simple fixed dates. Installing the Clock enables deliberate passage with `runFor()`, `fastForward()`, `pauseAt()`, and `resume()`, which makes time-dependent UI tests deterministic.

Why would you choose runFor over fastForward?

I choose `runFor()` when intermediate callbacks matter, such as each countdown tick or repeated polling. I choose `fastForward()` for a sleep-like jump where overdue timers should fire at most once. The product's recovery model determines the choice.

What installation-order rule prevents fake timer bugs?

Call `clock.install()` before navigation or any application code that creates supported time callbacks. Installing after native timers exist mixes implementations and can lead to undefined behavior. Shared fixtures must preserve that order visibly.

How would you test a token expiry flow?

I control the browser instant and seed a token or mocked API response with an explicit expiry instant. I cover before, exact, and after boundary cases, then assert protected UI behavior. A separate server-side test verifies backend enforcement because browser Clock cannot move server time.

How do locale and timezone affect date assertions?

A mocked date represents an instant. The timezone maps it to local calendar fields, and locale formats those fields. I configure all three explicitly and avoid punctuation-sensitive assertions unless exact formatting is a product contract.

What would you inspect when advancing Clock produces no UI change?

I verify installation happened before timers were created, identify whether the feature uses browser timers or an external scheduler, and inspect pending network activity. I also check whether the chosen method models callbacks correctly. Trace and browser console evidence usually reveal the missing boundary.

How do you prevent mocked time from leaking between tests?

I retain Playwright Test's isolated BrowserContext per test and avoid context reuse. If I manually create a context, I close it in a fixture. I do not depend on a nonexistent uninstall method or global machine-time changes.

Frequently Asked Questions

How do I mock the current date in Playwright?

Call `await page.clock.setFixedTime(new Date('2026-07-13T09:00:00Z'))` before navigation. Browser calls to `Date.now()` and zero-argument `new Date()` then return that fixed instant while timers continue running.

How do I fast forward time in Playwright?

Install the Clock before navigation, then call `page.clock.fastForward()` with milliseconds or a supported duration string such as `'05:00'`. This models a time jump and fires due timers at most once.

What is the difference between setFixedTime and install?

`setFixedTime()` is a simple fixed-Date mode with timers still running. `install()` replaces supported time functions so the test can control timer passage consistently with methods such as `runFor()` and `pauseAt()`.

Does Playwright Clock affect all pages in a test?

Clock is installed for the entire BrowserContext, so pages and iframes in that context share it. It does not affect a separate context, the Node.js test runner, or remote services.

Can Playwright mock a timezone?

Configure `timezoneId` in the browser context or Playwright Test `use` options. Clock supplies the instant, while the timezone controls local representation and locale controls formatting.

Why does my mocked time test pass locally but fail in CI?

Common causes are offset-free date strings, uncontrolled timezone or locale, late clock installation, and a backend that uses real time. Inspect those inputs and trace evidence before broadening assertions or timeouts.

Can I uninstall Playwright Clock during a test?

There is no supported `clock.uninstall()` method. Rely on Playwright Test's fresh BrowserContext per test, or close a manually created context when the scenario finishes.

Related Guides