mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-09-14 19:45:30 +08:00
feat: add screencast fps option (#2312)
Addresses #2291. ## Summary - add an `--experimental-screencast-fps` option for screencast recording - validate the value as a positive integer and imply `--experimental-screencast` - pass the configured FPS through to Puppeteer's `screencast()` call - update generated option docs and flag telemetry metadata ## Testing - `git diff --check origin/main...HEAD` - `npm run check-format` - `npm run typecheck` - `npm test -- --test-only tests/tools/screencast.test.ts tests/cli.test.ts`
This commit is contained in:
@@ -704,6 +704,11 @@ The Chrome DevTools MCP server supports the following configuration option:
|
||||
- **Type:** string
|
||||
- **Default:** `false`
|
||||
|
||||
- **`--experimentalScreencastFps`/ `--experimental-screencast-fps`**
|
||||
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.
|
||||
- **Type:** number
|
||||
- **Default:** `false`
|
||||
|
||||
- **`--categoryExperimentalWebmcp`/ `--category-experimental-webmcp`**
|
||||
Set to true to enable debugging WebMCP tools. Requires Chrome 150+ with the following flag: `--enable-features=WebMCP`
|
||||
- **Type:** boolean
|
||||
|
||||
@@ -206,6 +206,23 @@ export const mcpOptions = {
|
||||
describe: 'Path to ffmpeg executable for screencast recording.',
|
||||
implies: 'experimentalScreencast',
|
||||
},
|
||||
experimentalScreencastFps: {
|
||||
type: 'number',
|
||||
describe:
|
||||
'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.',
|
||||
implies: 'experimentalScreencast',
|
||||
coerce: (value: number | undefined) => {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!Number.isInteger(value) || value <= 0) {
|
||||
throw new Error(
|
||||
`Invalid experimentalScreencastFps ${value}. Expected a positive integer.`,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
},
|
||||
categoryExperimentalWebmcp: {
|
||||
type: 'boolean',
|
||||
describe:
|
||||
|
||||
@@ -397,5 +397,9 @@
|
||||
{
|
||||
"name": "javascript_evaluation",
|
||||
"flagType": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "experimental_screencast_fps_present",
|
||||
"flagType": "boolean"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -89,6 +89,7 @@ export const startScreencast = definePageTool(args => ({
|
||||
path: resolvedPath,
|
||||
format: format,
|
||||
ffmpegPath: args?.experimentalFfmpegPath,
|
||||
fps: args?.experimentalScreencastFps,
|
||||
});
|
||||
} catch (err) {
|
||||
// If we generated a temporary directory for this recording, remove it so
|
||||
|
||||
+16
-1
@@ -7,7 +7,7 @@
|
||||
import assert from 'node:assert';
|
||||
import {describe, it} from 'node:test';
|
||||
|
||||
import {parser} from '../src/config/mcp-options.js';
|
||||
import {mcpOptions, parser} from '../src/config/mcp-options.js';
|
||||
|
||||
function parseArguments(argv: string[], env: NodeJS.ProcessEnv = {}) {
|
||||
return parser('0.0.0', ['node', 'main.js', ...argv], env)
|
||||
@@ -232,6 +232,21 @@ describe('cli args parsing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects invalid screencast fps values', async () => {
|
||||
const coerce = mcpOptions.experimentalScreencastFps.coerce;
|
||||
assert.ok(coerce);
|
||||
|
||||
assert.strictEqual(coerce(undefined), undefined);
|
||||
assert.strictEqual(coerce(10), 10);
|
||||
|
||||
for (const value of [0, -1, 10.5, Number.NaN]) {
|
||||
assert.throws(
|
||||
() => coerce(value),
|
||||
/Invalid experimentalScreencastFps .* Expected a positive integer\./,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('parses usage statistics flag', async () => {
|
||||
// Test default (should be true).
|
||||
const defaultArgs = parseArguments(['main.js'], {});
|
||||
|
||||
@@ -12,7 +12,7 @@ import {describe, it, afterEach} from 'node:test';
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import type {ParsedArguments} from '../../src/config/mcp-options.js';
|
||||
import {parseArguments} from '../../src/config/mcp-options.js';
|
||||
import {startScreencast, stopScreencast} from '../../src/tools/screencast.js';
|
||||
import {withMcpContext} from '../utils.js';
|
||||
|
||||
@@ -209,9 +209,13 @@ describe('screencast', () => {
|
||||
.resolves(mockRecorder as never);
|
||||
|
||||
const experimentalFfmpegPath = '/custom/path/to/ffmpeg';
|
||||
await startScreencast({
|
||||
experimentalFfmpegPath,
|
||||
} as ParsedArguments).handler(
|
||||
const args = parseArguments('test', [
|
||||
'node',
|
||||
'test',
|
||||
'--experimental-screencast',
|
||||
`--experimental-ffmpeg-path=${experimentalFfmpegPath}`,
|
||||
]);
|
||||
await startScreencast(args).handler(
|
||||
{params: {}, page: context.getSelectedMcpPage()},
|
||||
response,
|
||||
context,
|
||||
@@ -222,6 +226,32 @@ describe('screencast', () => {
|
||||
assert.strictEqual(callArgs?.ffmpegPath, experimentalFfmpegPath);
|
||||
});
|
||||
});
|
||||
|
||||
it('passes screencast fps from args to puppeteer', async () => {
|
||||
await withMcpContext(async (response, context) => {
|
||||
const mockRecorder = createMockRecorder();
|
||||
const selectedPage = context.getSelectedMcpPage().pptrPage;
|
||||
const screencastStub = sinon
|
||||
.stub(selectedPage, 'screencast')
|
||||
.resolves(mockRecorder as never);
|
||||
|
||||
const args = parseArguments('test', [
|
||||
'node',
|
||||
'test',
|
||||
'--experimental-screencast',
|
||||
'--experimental-screencast-fps=10',
|
||||
]);
|
||||
await startScreencast(args).handler(
|
||||
{params: {}, page: context.getSelectedMcpPage()},
|
||||
response,
|
||||
context,
|
||||
);
|
||||
|
||||
sinon.assert.calledOnce(screencastStub);
|
||||
const callArgs = screencastStub.firstCall.args[0];
|
||||
assert.strictEqual(callArgs?.fps, 10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('screencast_stop', () => {
|
||||
|
||||
Reference in New Issue
Block a user