Skip to content

Commit ff8a821

Browse files
committed
feat: add screencast fps option
1 parent c645eee commit ff8a821

6 files changed

Lines changed: 80 additions & 5 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,11 @@ The Chrome DevTools MCP server supports the following configuration option:
663663
- **Type:** string
664664
- **Default:** `false`
665665

666+
- **`--experimentalScreencastFps`/ `--experimental-screencast-fps`**
667+
Frames per second to use for screencast recording. Lower values can reduce memory pressure on pages that produce frames faster than ffmpeg can encode them.
668+
- **Type:** number
669+
- **Default:** Uses Puppeteer's screencast default.
670+
666671
- **`--categoryExperimentalWebmcp`/ `--category-experimental-webmcp`**
667672
Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`
668673
- **Type:** boolean

src/bin/chrome-devtools-mcp-cli-options.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,23 @@ export const cliOptions = {
208208
describe: 'Path to ffmpeg executable for screencast recording.',
209209
implies: 'experimentalScreencast',
210210
},
211+
experimentalScreencastFps: {
212+
type: 'number',
213+
describe:
214+
'Frames per second to use for screencast recording. Lower values can reduce memory pressure on pages that produce frames faster than ffmpeg can encode them.',
215+
implies: 'experimentalScreencast',
216+
coerce: (value: number | undefined) => {
217+
if (value === undefined) {
218+
return;
219+
}
220+
if (!Number.isInteger(value) || value <= 0) {
221+
throw new Error(
222+
`Invalid experimentalScreencastFps ${value}. Expected a positive integer.`,
223+
);
224+
}
225+
return value;
226+
},
227+
},
211228
categoryExperimentalWebmcp: {
212229
type: 'boolean',
213230
describe:

src/telemetry/flag_usage_metrics.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,5 +369,9 @@
369369
{
370370
"name": "allow_unrestricted_paths",
371371
"flagType": "boolean"
372+
},
373+
{
374+
"name": "experimental_screencast_fps_present",
375+
"flagType": "boolean"
372376
}
373377
]

src/tools/screencast.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export const startScreencast = definePageTool(args => ({
8787
path: resolvedPath,
8888
format: format,
8989
ffmpegPath: args?.experimentalFfmpegPath,
90+
fps: args?.experimentalScreencastFps,
9091
});
9192
} catch (err) {
9293
// If we generated a temporary directory for this recording, remove it so

tests/cli.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
import assert from 'node:assert';
88
import {describe, it} from 'node:test';
99

10-
import {parseArguments} from '../src/bin/chrome-devtools-mcp-cli-options.js';
10+
import {
11+
cliOptions,
12+
parseArguments,
13+
} from '../src/bin/chrome-devtools-mcp-cli-options.js';
1114

1215
describe('cli args parsing', () => {
1316
const defaultArgs = {
@@ -278,6 +281,21 @@ describe('cli args parsing', () => {
278281
});
279282
});
280283

284+
it('rejects invalid screencast fps values', async () => {
285+
const coerce = cliOptions.experimentalScreencastFps.coerce;
286+
assert.ok(coerce);
287+
288+
assert.strictEqual(coerce(undefined), undefined);
289+
assert.strictEqual(coerce(10), 10);
290+
291+
for (const value of [0, -1, 10.5, Number.NaN]) {
292+
assert.throws(
293+
() => coerce(value),
294+
/Invalid experimentalScreencastFps .* Expected a positive integer\./,
295+
);
296+
}
297+
});
298+
281299
it('parses usage statistics flag', async () => {
282300
// Test default (should be true).
283301
const defaultArgs = parseArguments('1.0.0', ['node', 'main.js'], {});

tests/tools/screencast.test.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {describe, it, afterEach} from 'node:test';
1212

1313
import sinon from 'sinon';
1414

15-
import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
15+
import {parseArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js';
1616
import {startScreencast, stopScreencast} from '../../src/tools/screencast.js';
1717
import {withMcpContext} from '../utils.js';
1818

@@ -209,9 +209,13 @@ describe('screencast', () => {
209209
.resolves(mockRecorder as never);
210210

211211
const experimentalFfmpegPath = '/custom/path/to/ffmpeg';
212-
await startScreencast({
213-
experimentalFfmpegPath,
214-
} as ParsedArguments).handler(
212+
const args = parseArguments('test', [
213+
'node',
214+
'test',
215+
'--experimental-screencast',
216+
`--experimental-ffmpeg-path=${experimentalFfmpegPath}`,
217+
]);
218+
await startScreencast(args).handler(
215219
{params: {}, page: context.getSelectedMcpPage()},
216220
response,
217221
context,
@@ -222,6 +226,32 @@ describe('screencast', () => {
222226
assert.strictEqual(callArgs?.ffmpegPath, experimentalFfmpegPath);
223227
});
224228
});
229+
230+
it('passes screencast fps from args to puppeteer', async () => {
231+
await withMcpContext(async (response, context) => {
232+
const mockRecorder = createMockRecorder();
233+
const selectedPage = context.getSelectedPptrPage();
234+
const screencastStub = sinon
235+
.stub(selectedPage, 'screencast')
236+
.resolves(mockRecorder as never);
237+
238+
const args = parseArguments('test', [
239+
'node',
240+
'test',
241+
'--experimental-screencast',
242+
'--experimental-screencast-fps=10',
243+
]);
244+
await startScreencast(args).handler(
245+
{params: {}, page: context.getSelectedMcpPage()},
246+
response,
247+
context,
248+
);
249+
250+
sinon.assert.calledOnce(screencastStub);
251+
const callArgs = screencastStub.firstCall.args[0];
252+
assert.strictEqual(callArgs?.fps, 10);
253+
});
254+
});
225255
});
226256

227257
describe('screencast_stop', () => {

0 commit comments

Comments
 (0)