Skip to main content

Basics

Global Cache stores a value under a key and returns that value to every test worker that requests the same key. Use a static key for one shared value, a dynamic key when the value depends on inputs, and a persistent key when the value should be reused across Playwright executions.

Static keys

Call globalCache.get(key, computeFn) with a fixed key when every caller should share one value:

tests/users.spec.ts
import { test, expect } from '@playwright/test';
import { globalCache } from '@global-cache/playwright';

let userId: string;

test.beforeAll(async () => {
userId = await globalCache.get('seeded-user-id', async () => {
const user = await database.createUser();
return user.id;
});
});

test('opens user profile', async ({ page }) => {
await page.goto(`/users/${userId}/profile`);
await expect(page).toHaveURL(/\/profile$/);
});

test('opens user dashboard', async ({ page }) => {
await page.goto(`/users/${userId}/dashboard`);
await expect(page).toHaveURL(/\/dashboard$/);
});

The first worker requesting seeded-user-id creates the user. Concurrent workers wait, and every worker receives the same stored userId. A static, non-persistent value remains available until the Playwright execution ends.

The computed value crosses an HTTP boundary, so return primitives, arrays, or plain JSON-compatible objects. Do not cache a Playwright Page, BrowserContext, database connection, or class instance; cache the data needed to recreate or configure it instead.

You can call get() from hooks, fixtures, helper functions, or route handlers. See Hooks and Fixtures for the recommended patterns in Playwright setup code.

Dynamic keys

If a computed value depends on one or more parameters, include every parameter in the cache key. This gives each parameter combination a separate cache entry, so callers only reuse a value that matches their inputs:

const userData = await globalCache.get(`user-${userId}`, async () => {
return api.getUser(userId);
});

Without userId in the key, the first loaded account would be returned for every later request.

Use stable, deterministic key parts. Avoid random values, timestamps, or worker indexes unless they are genuinely part of the logical identity; those values prevent workers from sharing the entry.

Namespacing keys

Prefixes make dynamic values easy to group and clean up:

const userId = await globalCache.get(`created-user:${role}`, () => createUser(role));

The matching cleanup callback can retrieve all old values:

const userIds = await globalCache.getStaleList<string>('created-user:');

See Cleanup for a complete cleanup callback.

Persistent keys

By default, keys live in memory for one Playwright execution. Pass a time to live (TTL) to persist a key's value on the filesystem and reuse it in later runs:

const authState = await globalCache.get('auth-state', { ttl: '1 hour' }, async () => {
const page = await browser.newPage();
await signIn(page);
return page.context().storageState();
});

TTL accepts:

  • A number of milliseconds.
  • An ms-compatible string such as '5 min' or '1 hour'.
  • 'infinite' for no time-based expiration.

The default directory is .global-cache:

.global-cache/
└── auth-state.json

Add this directory to .gitignore, or choose another path with basePath.

Recalculation

The value for a persistent key is recalculated when its TTL expires, its computation signature changes, or the key is deleted. The first worker to encounter the invalid value performs the new computation while other workers wait.

To force recalculation, delete the corresponding file in .global-cache or call delete():

await globalCache.delete('auth-state');

delete() removes both the in-memory and filesystem entries.

CI

Filesystem persistence often provides no benefit on an ephemeral CI runner. Set ignoreTTL: true to keep the same application code while treating all keys as run-scoped:

globalCache.config({
ignoreTTL: Boolean(process.env.CI),
});