npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

playwright-clock-helper

v0.2.0

Published

Deterministic browser time control fixtures and helpers for Playwright Test.

Readme

playwright-clock-helper

Deterministic browser-time fixtures and helpers for Playwright Test. The package centralizes clock lifecycle, strict input validation, mode-safe operations, and failure diagnostics while keeping runtime dependencies at zero.

Status: 0.1.x is an API validation release. Minor releases may contain public API changes, documented in the changelog.

Install

This is a public npmjs package, so no registry-specific .npmrc or read token is required.

npm install --save-dev playwright-clock-helper @playwright/test

Requirements: Node.js 20 or newer and @playwright/test >=1.45.0 <2. The package is ESM-only.

Quick start

import { expect, test } from "playwright-clock-helper";

test.use({
  testTime: {
    mode: "controlled",
    now: "2026-07-01T00:00:00Z",
  },
});

test("expires the client-side session warning", async ({ page, time }) => {
  await page.goto("/dashboard");
  await time.pauseAt("2026-07-01T01:00:00Z");
  await time.jumpBy("30m");

  await expect(page.getByText("Session expired")).toBeVisible();
});

Clock control is opt-in. Without testTime, the default is { mode: 'off' }; the helper does not parse time, call Playwright Clock, or attach diagnostics.

Modes and operations

| Mode | Browser behavior | Allowed controller operations | | ------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------- | | off | Real browser time; no Clock call | None. Requesting time fails before the test body. | | fixed | Date.now() and new Date() are fixed; timers keep running in real time | setFixedTime() | | controlled | Date and timer APIs are controlled by Playwright Clock | pauseAt(), jumpBy(), tickBy(), tickUntil(), resume() |

jumpBy() maps to Playwright Clock.fastForward(). It jumps to the destination and fires each due timer at most once, which resembles reopening a sleeping laptop.

tickBy() maps to Clock.runFor(). It processes timers and related callbacks throughout the interval. Use it when intermediate interval, animation, or countdown updates matter.

tickUntil()

tickUntil() evaluates an application condition and advances controlled virtual time until that condition returns exactly true:

await time.tickUntil(
  async () => (await page.getByTestId("status").textContent()) === "ready",
  {
    step: "100ms",
    max: "5s",
  },
);

The predicate runs before the first clock advance, so an already-matched condition succeeds without changing time. Both step and max are required. step must be greater than zero; max may be zero. When max is not evenly divisible by step, the final advance is shortened so virtual time never exceeds max. The predicate is evaluated once more after that final advance.

The predicate may be synchronous or asynchronous, but must return exactly a boolean. A thrown/rejected predicate error and a Playwright Clock error are propagated immediately. If the condition remains false at max, TimeConditionTimeoutError is thrown with attempts, elapsedMs, stepMs, and maxMs fields:

import { TimeConditionTimeoutError } from "playwright-clock-helper/core";

To prevent accidental runaway loops, one call may advance the clock at most 10,000 times. Larger max / step combinations throw TimeInputError before the predicate runs.

tickUntil() is exclusive. It waits for controller operations that were called first, but later controller calls fail immediately with TimeLifecycleError until polling finishes. Its predicate must only observe application state; do not call time.* methods from inside it.

Wrong-mode operations throw TimeModeError; the helper never changes modes implicitly. Create a new test/BrowserContext to use another mode.

Fixed time

test.use({
  testTime: {
    mode: "fixed",
    now: "2026-07-01T09:00:00+09:00",
  },
});

test("changes the displayed date", async ({ page, time }) => {
  await page.goto("/today");
  await time.setFixedTime("2026-07-02T00:00:00Z");
  await page.getByRole("button", { name: "Reload" }).click();
});

Clock lifecycle

Playwright exposes Clock as page.clock, but installs it for the entire BrowserContext. The fixture creates a short-lived internal setup page, installs the Context clock, closes that page, and only then exposes context to downstream fixtures. Consequently, application pages, navigation, and timer registration happen after clock setup.

Do not mix this package with direct page.clock.* calls in the same Context. Direct calls cannot be detected reliably.

The low-level API is available when you own the Context lifecycle:

import { setupTestClock } from "playwright-clock-helper/core";

const context = await browser.newContext();
const time = await setupTestClock(context, {
  mode: "controlled",
  now: "2026-07-01T00:00:00Z",
});

Call it before creating any application page. Calling it after a page exists, or attempting to replace an installed mode/initial time, throws TimeLifecycleError.

Fixture composition

Apply time fixtures first, then extend the returned test with application fixtures:

import type { Page } from "@playwright/test";
import { test as base, expect } from "@playwright/test";
import { extendWithTime } from "playwright-clock-helper/fixtures";

const timeBase = extendWithTime(base);

export const test = timeBase.extend<{ authenticatedPage: Page }>({
  authenticatedPage: async ({ page }, use) => {
    await page.goto("/login"); // Clock setup has already completed.
    // Authenticate...
    await use(page);
  },
});

export { expect };

Avoid unordered mergeTests() composition when another fixture overrides context/page or navigates before clock setup.

Configuration

type TestTimeConfig =
  | { mode: "off" }
  | {
      mode: "fixed" | "controlled";
      now: Date | number | string;
      diagnostics?: "off" | "on-failure" | "always";
    };

now is mandatory in active modes. Missing or unsupported configuration throws TimeConfigurationError.

Instant inputs

Accepted:

new Date("2026-07-01T00:00:00Z");
1_782_864_000_000; // Unix epoch milliseconds
("2026-07-01T00:00:00Z");
("2026-07-01T09:00:00.123+09:00");

Strings must be ISO 8601 with Z or an explicit ±HH:MM UTC offset. Timezone-less values such as 2026-07-01T09:00:00, invalid calendar dates, NaN, and infinity throw TimeInputError.

Duration inputs

Numbers are non-negative safe integer milliseconds. Strings are a non-negative integer plus one of ms, s, m, h, or d:

500;
("250ms");
("10s");
("5m");
("2h");
("1d");

1d always means 24 hours, not the next local calendar day. Use pauseAt() for calendar/DST boundaries. Negative, fractional, variable-length (month, year), and Playwright clock-format strings are rejected.

The same parsers are exported from playwright-clock-helper/core as normalizeInstant() and parseDuration().

Browser timezone

testTime.now selects an absolute instant. Playwright timezoneId independently controls how the browser displays and calculates local civil time:

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

export default defineConfig({
  use: {
    locale: "ja-JP",
    timezoneId: "Asia/Tokyo",
  },
});

The helper does not change timezoneId.

Browser/server boundary

Clock affects browser JavaScript in the configured BrowserContext, including pages, popups, and iframes. Depending on the installed Playwright version, this includes Date, timers, animation/idle callbacks, performance, and event timestamps.

It does not change the Playwright runner, Node.js helpers, API servers, databases, Redis/JWT TTLs, queues, external services, the OS clock, or another BrowserContext. Server expiration scenarios need a server-side test clock, test-only API, mock, or request-scoped time injection.

Diagnostics

Active modes default to diagnostics: 'on-failure'. The fixture attaches playwright-clock-operations.json when the actual test status differs from the expected status. Use always for every result or off to disable it.

The JSON contains package/Playwright versions, mode, normalized initial time, operation history, browser, and project. A tick-until entry records one aggregate result (matched, max-reached, predicate-error, or clock-error) with its attempt and virtual-time counts. It does not capture predicate values or errors, pages, cookies, headers, tokens, or application data. Attachment failures during teardown are contained so they do not replace the original test failure.

Public entry points

| Import | Contents | | -------------------------------------- | ----------------------------------------------------- | | playwright-clock-helper | Configured test, Playwright expect, primary types | | playwright-clock-helper/fixtures | extendWithTime() and fixture types | | playwright-clock-helper/core | Setup, parsers, controller/error types | | playwright-clock-helper/experimental | Type-only unstable extension contracts |

Package exports blocks internal deep imports.

Troubleshooting

time requested while mode is off

Add test.use({ testTime: { mode: 'fixed' | 'controlled', now: ... } }) to the test or enclosing describe.

Setup says application pages already exist

Call extendWithTime(base) before app fixtures. Do not call setupTestClock() from the test body.

A server-side session/JWT still has its original expiry

This package changes browser time only. Use a server-side test mechanism too.

Intervals fire once after a long advance

That is jumpBy()/fastForward() behavior. Use tickBy() when every interval callback matters.

Local and CI display different dates

Use offset-bearing now values and configure the same Playwright timezoneId in every environment.

Development

npm ci
npm run verify
npx playwright install chromium firefox webkit
npm run test:integration
npm run consumer:test

The consumer smoke test packs the package, installs that tarball into a clean temporary project, type-checks every public entry point, and imports the runtime entry points.

Publishing publicly to npmjs

Releases use npm Trusted Publishing from GitHub Actions. No long-lived npm publish token is stored in GitHub. Stable vX.Y.Z GitHub Releases from main require approval in the npm environment before publishing.

The release conditions, approval policy, one-time 0.1.0 bootstrap, operating procedure, and verification boundary are documented in the npm release policy.

License

MIT