microsoft/playwright · error
"recordVideo.fps" must be a positive number, got
Error message
"recordVideo.fps" must be a positive number, got ${options.recordVideo.fps} What it means
When creating a browser context with recordVideo, Playwright validates the video frame rate. If recordVideo.fps is provided it must be a number strictly greater than 0; zero or negative values are rejected because a non-positive fps is meaningless for video recording.
Solutions
- Set recordVideo.fps to a positive number (e.g. 25 or 30).
- Omit fps entirely to use Playwright's default — only 'dir' is required.
- Validate the source of fps (config/env) and fall back to undefined instead of passing 0.
Example fix
// before
const ctx = await browser.newContext({ recordVideo: { dir: 'videos/', fps: Number(process.env.FPS) || 0 } });
// after
const fps = Number(process.env.FPS);
const ctx = await browser.newContext({ recordVideo: { dir: 'videos/', ...(fps > 0 ? { fps } : {}) } }); Defensive patterns
Strategy: validation
Validate before calling
const fps = options.recordVideo?.fps;
if (fps !== undefined && (!(typeof fps === 'number') || fps <= 0))
throw new Error(`recordVideo.fps must be a positive number, got ${fps}`); Type guard
function isValidFps(fps: unknown): fps is number {
return typeof fps === 'number' && Number.isFinite(fps) && fps > 0;
} Try / catch
try {
context = await browser.newContext({ recordVideo: { dir: 'videos/', fps } });
} catch (e) {
if ((e as Error).message.includes('recordVideo.fps')) {
context = await browser.newContext({ recordVideo: { dir: 'videos/' } }); // default fps
} else throw e;
} Prevention
- Omit fps unless you need a non-default frame rate.
- Coerce env/config fps with Number() and guard `> 0` before passing it.
- Treat 0 as 'unset', never as a valid fps.
- Add a config schema check (e.g. zod) for recordVideo options.
When it happens
Trigger: browser.newContext or launchPersistentContext with options like { recordVideo: { dir: 'videos/', fps: 0 } } or a negative fps, typically from a computed or config/env-derived number.
Common situations: Computing fps from a config or environment variable that defaults to 0 ('0 = default' assumption), division producing 0 or a negative value, or hand-editing examples that omit fps.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- Browser userDataDir is not supported in isolated mode.
- Cannot use --mobile together with --device, pick one.
- clientCertificates.origin is required
- Device emulation is not supported with cdpEndpoint.
- "deviceScaleFactor" option is not supported with null…
AI-assisted analysis of microsoft/playwright@f1d33b5029 (2026-09-21).
Data as JSON: /api/errors/a12564db1bd99b6c.
Report an issue: GitHub.
Appendix: source
Thrown at packages/playwright-core/src/server/browserContext.ts:787
export function validateBrowserContextOptions(options: types.BrowserContextOptions, browserOptions: BrowserOptions) {
if (options.noDefaultViewport && options.deviceScaleFactor !== undefined)
throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`);
if (options.noDefaultViewport && !!options.isMobile)
throw new Error(`"isMobile" option is not supported with null "viewport"`);
if (options.acceptDownloads === undefined && browserOptions.name !== 'electron')
options.acceptDownloads = 'accept';
// Electron requires explicit acceptDownloads: true since we wait for
// https://github.com/electron/electron/pull/41718 to be widely shipped.
// In 6-12 months, we can remove this check.
else if (options.acceptDownloads === undefined && browserOptions.name === 'electron')
options.acceptDownloads = 'internal-browser-default';
if (!options.viewport && !options.noDefaultViewport)
options.viewport = { width: 1280, height: 720 };
if (options.proxy)
options.proxy = normalizeProxySettings(options.proxy);
if (options.recordVideo?.fps !== undefined && options.recordVideo.fps <= 0)
throw new Error(`"recordVideo.fps" must be a positive number, got ${options.recordVideo.fps}`);
verifyGeolocation(options.geolocation);
}
export function findMatchingHttpCredentials(credentials: HttpCredentials[] | undefined, url: string): HttpCredentials | undefined {
const origin = new URL(url).origin.toLowerCase();
return credentials?.find(c => !c.origin || c.origin.toLowerCase() === origin);
}
export function verifyGeolocation(geolocation?: types.Geolocation): asserts geolocation is types.Geolocation {
if (!geolocation)
return;
geolocation.accuracy = geolocation.accuracy || 0;
const { longitude, latitude, accuracy } = geolocation;
if (longitude < -180 || longitude > 180)
throw new Error(`geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.`);
if (latitude < -90 || latitude > 90)
throw new Error(`geolocation.latitude: precondition -90 <= LATITUDE <= 90 failed.`);
if (accuracy < 0)View on GitHub (pinned to f1d33b5029)