@playwright-labs/fixture-timers
v1.0.1
Published
Timers nodejs api support for Playwright
Downloads
129
Maintainers
Readme
@playwright-labs/fixture-timers
Promise-based Node.js timer APIs as Playwright test fixtures, with custom matchers for timing assertions.
Installation
npm install @playwright/test @playwright-labs/fixture-timers # npm
pnpm add @playwright/test @playwright-labs/fixture-timers # pnpm
yarn add @playwright/test @playwright-labs/fixture-timers # yarnFeatures
- Promise-based
setTimeout,setInterval,setImmediate, andscheduleras Playwright fixtures - Async iterator support for
setInterval(usefor await...of) AbortSignalsupport on all timer fixtures- 6 custom expect matchers for timing assertions
- Generic type support (
setTimeout<T>resolves toPromise<T>) - Composable with other fixtures via
mergeTests/mergeExpects
Fixtures
Merging with your test setup
// fixture.ts
import { mergeTests, mergeExpects } from "@playwright/test";
import {
test as timersTest,
expect as timersExpect,
} from "@playwright-labs/fixture-timers";
export const test = mergeTests(timersTest);
export const expect = mergeExpects(timersExpect);Or use directly:
import { test, expect } from "@playwright-labs/fixture-timers";setTimeout
Promise-based timeout that resolves after a delay with an optional value.
import { test, expect } from "@playwright-labs/fixture-timers";
test("resolves with value after delay", async ({ setTimeout }) => {
const result = await setTimeout(100, "hello");
expect(result).toBe("hello");
});
test("basic delay", async ({ setTimeout }) => {
await setTimeout(500); // wait 500ms
});Signature:
setTimeout<T = void>(delay?: number, value?: T, options?: TimerOptions) => Promise<T>setInterval
Returns an async iterator that yields a value at regular intervals. Use for await...of to consume it, and call .return?.() to clean up.
import { test, expect } from "@playwright-labs/fixture-timers";
test("poll at intervals", async ({ setInterval }) => {
const poller = setInterval(200, "tick");
let count = 0;
for await (const value of poller) {
expect(value).toBe("tick");
count++;
if (count >= 3) break;
}
expect(count).toBe(3);
});
test("manual iteration", async ({ setInterval }) => {
const iterator = setInterval(100, "value");
const first = await iterator.next();
expect(first.value).toBe("value");
expect(first.done).toBe(false);
await iterator.return?.(); // cleanup
});Signature:
setInterval<T = void>(delay?: number, value?: T, options?: TimerOptions) => NodeJS.AsyncIterator<T>setImmediate
Promise that resolves on the next iteration of the event loop, before setTimeout(0).
import { test, expect } from "@playwright-labs/fixture-timers";
test("immediate resolves before timeout", async ({ setImmediate, setTimeout }) => {
const order: string[] = [];
const t = setTimeout(0).then(() => { order.push("timeout"); });
const i = setImmediate().then(() => { order.push("immediate"); });
await Promise.all([t, i]);
expect(order[0]).toBe("immediate");
});
test("resolves with value", async ({ setImmediate }) => {
const result = await setImmediate("fast");
expect(result).toBe("fast");
});Signature:
setImmediate<T = void>(value?: T, options?: TimerOptions) => Promise<T>scheduler
The Node.js scheduler API for precise timing control.
import { test } from "@playwright-labs/fixture-timers";
test("scheduler wait and yield", async ({ scheduler }) => {
await scheduler.wait(100); // wait 100ms
await scheduler.yield(); // yield control to the event loop
});Methods:
scheduler.wait(delay, options?)-- wait for a specified time (ms)scheduler.yield()-- yield control to the event loop
AbortSignal Support
All timer fixtures accept an options parameter with an optional signal for cancellation:
import { test, expect } from "@playwright-labs/fixture-timers";
test("abort cancels timer", async ({ setTimeout }) => {
const ac = new AbortController();
const promise = setTimeout(10000, "late", { signal: ac.signal });
ac.abort();
await expect(promise).rejects.toThrow();
});
test("abort cancels interval", async ({ setInterval }) => {
const ac = new AbortController();
const iterator = setInterval(100, "tick", { signal: ac.signal });
const first = await iterator.next();
expect(first.value).toBe("tick");
ac.abort();
await expect(iterator.next()).rejects.toThrow();
});Custom Matchers
toResolveWithin(maxMs)
Asserts that a promise resolves within a maximum time window.
test("operation completes within SLA", async ({ setTimeout }) => {
const promise = setTimeout(50, "done");
await expect(promise).toResolveWithin(100);
});toTakeAtLeast(minMs)
Asserts that a promise takes at least a minimum amount of time to resolve.
test("delay is enforced", async ({ setTimeout }) => {
const promise = setTimeout(100);
await expect(promise).toTakeAtLeast(95);
});toResolveWith(expectedValue)
Asserts that a promise resolves with a specific value using strict equality (===).
test("resolves with expected value", async ({ setTimeout }) => {
const promise = setTimeout(50, "test-value");
await expect(promise).toResolveWith("test-value");
});toResolveInTimeRange(minMs, maxMs)
Asserts that a promise resolves within a specific time window (not too fast, not too slow).
test("response within acceptable range", async ({ setTimeout }) => {
const promise = setTimeout(100, "result");
await expect(promise).toResolveInTimeRange(95, 150);
});toYield(expectedValue?)
Asserts that an async iterator yields a value. Optionally checks the yielded value with strict equality.
test("interval yields expected value", async ({ setInterval }) => {
const iterator = setInterval(100, "tick");
await expect(iterator).toYield("tick");
await expect(iterator).toYield(); // any value
await iterator.return?.();
});toYieldWithin(maxMs)
Asserts that an async iterator yields its next value within a time limit.
test("interval yields on time", async ({ setInterval }) => {
const iterator = setInterval(100);
await expect(iterator).toYieldWithin(150);
await iterator.return?.();
});Real-World Examples
Retry with Exponential Backoff
import { test, expect } from "@playwright-labs/fixture-timers";
test("retry with backoff", async ({ setTimeout }) => {
let attempts = 0;
const fetchWithRetry = async () => {
for (let i = 0; i < 3; i++) {
attempts++;
try {
const res = await fetch("/api/data");
if (res.ok) return res.json();
} catch {
// retry
}
await setTimeout(100 * Math.pow(2, i)); // 100ms, 200ms, 400ms
}
throw new Error("All retries exhausted");
};
await expect(fetchWithRetry()).toResolveWithin(1000);
});Polling with Async Iterator
test("poll until ready", async ({ setInterval }) => {
const poller = setInterval(500, "check");
for await (const _ of poller) {
const res = await fetch("/api/status");
const data = await res.json();
if (data.ready) break;
}
await poller.return?.();
});Timeout Race
test("operation vs timeout", async ({ setTimeout }) => {
const operation = fetch("/api/slow-endpoint");
const timeout = setTimeout(5000).then(() => {
throw new Error("Operation timed out");
});
const result = await Promise.race([operation, timeout]);
expect(result).toBeTruthy();
});Debounce Pattern
test("debounced action", async ({ setTimeout }) => {
let executeCount = 0;
let pending: Promise<void> | null = null;
const debounce = async (fn: () => void, delay: number) => {
pending = setTimeout(delay);
await pending;
fn();
};
await debounce(() => executeCount++, 100);
expect(executeCount).toBe(1);
});Coordinating Multiple Timers
test("sequential delays", async ({ setTimeout, setImmediate, scheduler }) => {
await setImmediate(); // yield control first
await scheduler.wait(50); // wait 50ms
const val = await setTimeout(100, 42); // wait 100ms, get 42
expect(val).toBe(42);
});API Reference
Fixtures
| Fixture | Type | Description |
| -------------- | ------------------------------------------------------------------------------- | --------------------------------------------- |
| setTimeout | <T = void>(delay?: number, value?: T, options?: TimerOptions) => Promise<T> | Promise-based timeout with optional value |
| setInterval | <T = void>(delay?: number, value?: T, options?: TimerOptions) => AsyncIterator<T> | Async iterator yielding at regular intervals |
| setImmediate | <T = void>(value?: T, options?: TimerOptions) => Promise<T> | Resolves on the next event loop iteration |
| scheduler | { wait(delay, options?): Promise<void>; yield(): Promise<void> } | Node.js scheduler API |
Custom Matchers
| Matcher | Input | Description |
| ------------------------------------ | ------------- | ---------------------------------------------- |
| toResolveWithin(maxMs) | Promise | Resolves within max time |
| toTakeAtLeast(minMs) | Promise | Takes at least min time to resolve |
| toResolveWith(value) | Promise | Resolves with exact value (strict ===) |
| toResolveInTimeRange(minMs, maxMs) | Promise | Resolves within a min/max time window |
| toYield(value?) | AsyncIterator | Yields a value (optionally matching) |
| toYieldWithin(maxMs) | AsyncIterator | Next yield happens within max time |
TypeScript Support
Full TypeScript support with type definitions included:
import { test, expect, type Fixture } from "@playwright-labs/fixture-timers";
test("typed example", async ({
setTimeout, // <T>(delay?, value?, options?) => Promise<T>
setInterval, // <T>(delay?, value?, options?) => AsyncIterator<T>
setImmediate, // <T>(value?, options?) => Promise<T>
scheduler, // { wait(delay, options?): Promise<void>; yield(): Promise<void> }
}) => {
const str = await setTimeout(100, "hello"); // inferred as string
const num = await setImmediate(42); // inferred as number
});