Skip to content

Commit da28be5

Browse files
committed
feat: add screencast fps option
1 parent 8432cb9 commit da28be5

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
@@ -664,6 +664,11 @@ The Chrome DevTools MCP server supports the following configuration option:
664664
- **Type:** string
665665
- **Default:** `false`
666666

667+
- **`--experimentalScreencastFps`/ `--experimental-screencast-fps`**
668+
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.
669+
- **Type:** number
670+
- **Default:** Uses Puppeteer's screencast default.
671+
667672
- **`--categoryExperimentalWebmcp`/ `--category-experimental-webmcp`**
668673
Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCP,DevToolsWebMCPSupport`
669674
- **Type:** boolean

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,23 @@ export const cliOptions = {
203203
describe: 'Path to ffmpeg executable for screencast recording.',
204204
implies: 'experimentalScreencast',
205205
},
206+
experimentalScreencastFps: {
207+
type: 'number',
208+
describe:
209+
'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.',
210+
implies: 'experimentalScreencast',
211+
coerce: (value: number | undefined) => {
212+
if (value === undefined) {
213+
return;
214+
}
215+
if (!Number.isInteger(value) || value <= 0) {
216+
throw new Error(
217+
`Invalid experimentalScreencastFps ${value}. Expected a positive integer.`,
218+
);
219+
}
220+
return value;
221+
},
222+
},
206223
categoryExperimentalWebmcp: {
207224
type: 'boolean',
208225
describe:

src/telemetry/flag_usage_metrics.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,5 +371,9 @@
371371
{
372372
"name": "allow_unrestricted_paths",
373373
"flagType": "boolean"
374+
},
375+
{
376+
"name": "experimental_screencast_fps_present",
377+
"flagType": "boolean"
374378
}
375379
]

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.getSelectedMcpPage().pptrPage;
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)