{"record":{"id":"a12564db1bd99b6c","repo":"microsoft/playwright","slug":"recordvideo-fps-must-be-a-positive-number-got-options","errorCode":null,"errorMessage":"\"recordVideo.fps\" must be a positive number, got ${options.recordVideo.fps}","messagePattern":"\"recordVideo\\.fps\" must be a positive number, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/browserContext.ts","lineNumber":787,"sourceCode":"\nexport function validateBrowserContextOptions(options: types.BrowserContextOptions, browserOptions: BrowserOptions) {\n  if (options.noDefaultViewport && options.deviceScaleFactor !== undefined)\n    throw new Error(`\"deviceScaleFactor\" option is not supported with null \"viewport\"`);\n  if (options.noDefaultViewport && !!options.isMobile)\n    throw new Error(`\"isMobile\" option is not supported with null \"viewport\"`);\n  if (options.acceptDownloads === undefined && browserOptions.name !== 'electron')\n    options.acceptDownloads = 'accept';\n  // Electron requires explicit acceptDownloads: true since we wait for\n  // https://github.com/electron/electron/pull/41718 to be widely shipped.\n  // In 6-12 months, we can remove this check.\n  else if (options.acceptDownloads === undefined && browserOptions.name === 'electron')\n    options.acceptDownloads = 'internal-browser-default';\n  if (!options.viewport && !options.noDefaultViewport)\n    options.viewport = { width: 1280, height: 720 };\n  if (options.proxy)\n    options.proxy = normalizeProxySettings(options.proxy);\n  if (options.recordVideo?.fps !== undefined && options.recordVideo.fps <= 0)\n    throw new Error(`\"recordVideo.fps\" must be a positive number, got ${options.recordVideo.fps}`);\n  verifyGeolocation(options.geolocation);\n}\n\nexport function findMatchingHttpCredentials(credentials: HttpCredentials[] | undefined, url: string): HttpCredentials | undefined {\n  const origin = new URL(url).origin.toLowerCase();\n  return credentials?.find(c => !c.origin || c.origin.toLowerCase() === origin);\n}\n\nexport function verifyGeolocation(geolocation?: types.Geolocation): asserts geolocation is types.Geolocation {\n  if (!geolocation)\n    return;\n  geolocation.accuracy = geolocation.accuracy || 0;\n  const { longitude, latitude, accuracy } = geolocation;\n  if (longitude < -180 || longitude > 180)\n    throw new Error(`geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.`);\n  if (latitude < -90 || latitude > 90)\n    throw new Error(`geolocation.latitude: precondition -90 <= LATITUDE <= 90 failed.`);\n  if (accuracy < 0)","sourceCodeStart":769,"sourceCodeEnd":805,"githubUrl":"https://github.com/microsoft/playwright/blob/f1d33b5029be8bbc1095fe88e559117fa3a6243b/packages/playwright-core/src/server/browserContext.ts#L769-L805","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst ctx = await browser.newContext({ recordVideo: { dir: 'videos/', fps: Number(process.env.FPS) || 0 } });\n// after\nconst fps = Number(process.env.FPS);\nconst ctx = await browser.newContext({ recordVideo: { dir: 'videos/', ...(fps > 0 ? { fps } : {}) } });","handlingStrategy":"validation","validationCode":"const fps = options.recordVideo?.fps;\nif (fps !== undefined && (!(typeof fps === 'number') || fps <= 0))\n  throw new Error(`recordVideo.fps must be a positive number, got ${fps}`);","typeGuard":"function isValidFps(fps: unknown): fps is number {\n  return typeof fps === 'number' && Number.isFinite(fps) && fps > 0;\n}","tryCatchPattern":"try {\n  context = await browser.newContext({ recordVideo: { dir: 'videos/', fps } });\n} catch (e) {\n  if ((e as Error).message.includes('recordVideo.fps')) {\n    context = await browser.newContext({ recordVideo: { dir: 'videos/' } }); // default fps\n  } else throw e;\n}","preventionTips":["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."],"tags":["video","config","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"f1d33b5029be8bbc1095fe88e559117fa3a6243b","analyzedAt":"2026-09-21T21:58:31.278Z","contentChangedAt":"2026-09-21T21:58:31.278Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}