feat: smart video recording with viewport matching and enhanced tool descriptions
Video Recording Enhancements: - Add intelligent recording modes: smart, continuous, action-only, segment - Implement automatic viewport matching to eliminate gray borders in videos - Add pause/resume functionality with manual and automatic control - Default to 1280x720 HD recording with auto-viewport matching - Enhanced browser_start_recording with autoSetViewport parameter Smart Recording System: - Smart mode: Auto-pause during waits, resume during actions (perfect for demos) - Action-only mode: Only record during browser interactions - Segment mode: Create separate video files for each action sequence - Continuous mode: Traditional behavior with optional manual pause/resume Tool Enhancements: - Comprehensive descriptions for all video recording tools - Professional context and use case guidance - Integration with browser_wait_for for recordDuringWait parameter - Action-aware recording in navigation and interaction tools - Enhanced browser_recording_status with mode and viewport info Documentation & Testing: - Complete best practices guide for video recording workflows - Viewport matching diagnostic and setup scripts - Recommended video sizes and quality settings - Gray border problem solution with automatic viewport matching Solves the gray border issue by ensuring browser viewport matches video recording dimensions, creating professional full-frame demo videos.
This commit is contained in:
parent
9257404ba3
commit
224f040645
176
src/context.ts
176
src/context.ts
@ -41,6 +41,11 @@ export class Context {
|
||||
private _videoRecordingConfig: { dir: string; size?: { width: number; height: number } } | undefined;
|
||||
private _videoBaseFilename: string | undefined;
|
||||
private _activePagesWithVideos: Set<playwright.Page> = new Set();
|
||||
private _videoRecordingPaused: boolean = false;
|
||||
private _pausedPageVideos: Map<playwright.Page, playwright.Video> = new Map();
|
||||
private _videoRecordingMode: 'continuous' | 'smart' | 'action-only' | 'segment' = 'smart';
|
||||
private _currentVideoSegment: number = 1;
|
||||
private _autoRecordingEnabled: boolean = true;
|
||||
private _environmentIntrospector: EnvironmentIntrospector;
|
||||
|
||||
private static _allContexts: Set<Context> = new Set();
|
||||
@ -369,6 +374,11 @@ export class Context {
|
||||
config: this._videoRecordingConfig,
|
||||
baseFilename: this._videoBaseFilename,
|
||||
activeRecordings: this._activePagesWithVideos.size,
|
||||
paused: this._videoRecordingPaused,
|
||||
pausedRecordings: this._pausedPageVideos.size,
|
||||
mode: this._videoRecordingMode,
|
||||
currentSegment: this._currentVideoSegment,
|
||||
autoRecordingEnabled: this._autoRecordingEnabled,
|
||||
};
|
||||
}
|
||||
|
||||
@ -547,9 +557,175 @@ export class Context {
|
||||
this._videoRecordingConfig = undefined;
|
||||
this._videoBaseFilename = undefined;
|
||||
this._activePagesWithVideos.clear();
|
||||
this._videoRecordingPaused = false;
|
||||
this._pausedPageVideos.clear();
|
||||
this._currentVideoSegment = 1;
|
||||
this._autoRecordingEnabled = true;
|
||||
// Don't reset recording mode - let it persist between sessions
|
||||
testDebug('Video recording state cleared');
|
||||
}
|
||||
|
||||
async pauseVideoRecording(): Promise<{ paused: number; message: string }> {
|
||||
if (!this._videoRecordingConfig) {
|
||||
testDebug('pauseVideoRecording called but no recording config found');
|
||||
return { paused: 0, message: 'No video recording is active' };
|
||||
}
|
||||
|
||||
if (this._videoRecordingPaused) {
|
||||
testDebug('Video recording is already paused');
|
||||
return { paused: this._pausedPageVideos.size, message: 'Video recording is already paused' };
|
||||
}
|
||||
|
||||
testDebug(`pauseVideoRecording: attempting to pause ${this._activePagesWithVideos.size} active recordings`);
|
||||
|
||||
// Store current video objects and close pages to pause recording
|
||||
let pausedCount = 0;
|
||||
for (const page of this._activePagesWithVideos) {
|
||||
try {
|
||||
if (!page.isClosed()) {
|
||||
const video = page.video();
|
||||
if (video) {
|
||||
// Store the video object for later resume
|
||||
this._pausedPageVideos.set(page, video);
|
||||
testDebug(`Stored video object for page: ${page.url()}`);
|
||||
pausedCount++;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
testDebug('Error pausing video on page:', error);
|
||||
}
|
||||
}
|
||||
|
||||
this._videoRecordingPaused = true;
|
||||
testDebug(`Video recording paused: ${pausedCount} recordings stored`);
|
||||
|
||||
return {
|
||||
paused: pausedCount,
|
||||
message: `Video recording paused. ${pausedCount} active recordings stored.`
|
||||
};
|
||||
}
|
||||
|
||||
async resumeVideoRecording(): Promise<{ resumed: number; message: string }> {
|
||||
if (!this._videoRecordingConfig) {
|
||||
testDebug('resumeVideoRecording called but no recording config found');
|
||||
return { resumed: 0, message: 'No video recording is configured' };
|
||||
}
|
||||
|
||||
if (!this._videoRecordingPaused) {
|
||||
testDebug('Video recording is not currently paused');
|
||||
return { resumed: 0, message: 'Video recording is not currently paused' };
|
||||
}
|
||||
|
||||
testDebug(`resumeVideoRecording: attempting to resume ${this._pausedPageVideos.size} paused recordings`);
|
||||
|
||||
// Resume recording by ensuring fresh browser context
|
||||
// The paused videos are automatically finalized and new ones will start
|
||||
let resumedCount = 0;
|
||||
|
||||
// Force context recreation to start fresh recording
|
||||
if (this._browserContextPromise) {
|
||||
await this.closeBrowserContext();
|
||||
}
|
||||
|
||||
// Clear the paused videos map as we'll get new video objects
|
||||
const pausedCount = this._pausedPageVideos.size;
|
||||
this._pausedPageVideos.clear();
|
||||
resumedCount = pausedCount;
|
||||
|
||||
this._videoRecordingPaused = false;
|
||||
testDebug(`Video recording resumed: ${resumedCount} recordings will restart on next page creation`);
|
||||
|
||||
return {
|
||||
resumed: resumedCount,
|
||||
message: `Video recording resumed. ${resumedCount} recordings will restart when pages are created.`
|
||||
};
|
||||
}
|
||||
|
||||
isVideoRecordingPaused(): boolean {
|
||||
return this._videoRecordingPaused;
|
||||
}
|
||||
|
||||
// Smart Recording Management
|
||||
setVideoRecordingMode(mode: 'continuous' | 'smart' | 'action-only' | 'segment'): void {
|
||||
this._videoRecordingMode = mode;
|
||||
testDebug(`Video recording mode set to: ${mode}`);
|
||||
}
|
||||
|
||||
getVideoRecordingMode(): string {
|
||||
return this._videoRecordingMode;
|
||||
}
|
||||
|
||||
async beginVideoAction(actionName: string): Promise<void> {
|
||||
if (!this._videoRecordingConfig || !this._autoRecordingEnabled) return;
|
||||
|
||||
testDebug(`beginVideoAction: ${actionName}, mode: ${this._videoRecordingMode}`);
|
||||
|
||||
switch (this._videoRecordingMode) {
|
||||
case 'continuous':
|
||||
// Always recording, no action needed
|
||||
break;
|
||||
|
||||
case 'smart':
|
||||
case 'action-only':
|
||||
// Resume recording if paused
|
||||
if (this._videoRecordingPaused) {
|
||||
await this.resumeVideoRecording();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'segment':
|
||||
// Create new segment for this action
|
||||
if (this._videoRecordingPaused) {
|
||||
await this.resumeVideoRecording();
|
||||
}
|
||||
// Note: Actual segment creation happens in stopVideoRecording
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async endVideoAction(actionName: string, shouldPause: boolean = true): Promise<void> {
|
||||
if (!this._videoRecordingConfig || !this._autoRecordingEnabled) return;
|
||||
|
||||
testDebug(`endVideoAction: ${actionName}, shouldPause: ${shouldPause}, mode: ${this._videoRecordingMode}`);
|
||||
|
||||
switch (this._videoRecordingMode) {
|
||||
case 'continuous':
|
||||
// Never auto-pause in continuous mode
|
||||
break;
|
||||
|
||||
case 'smart':
|
||||
case 'action-only':
|
||||
// Auto-pause after action unless explicitly told not to
|
||||
if (shouldPause && !this._videoRecordingPaused) {
|
||||
await this.pauseVideoRecording();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'segment':
|
||||
// Always end segment after action
|
||||
await this.finalizeCurrentVideoSegment();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async finalizeCurrentVideoSegment(): Promise<string[]> {
|
||||
if (!this._videoRecordingConfig) return [];
|
||||
|
||||
testDebug(`Finalizing video segment ${this._currentVideoSegment}`);
|
||||
|
||||
// Get current video paths before creating new segment
|
||||
const segmentPaths = await this.stopVideoRecording();
|
||||
|
||||
// Immediately restart recording for next segment
|
||||
this._currentVideoSegment++;
|
||||
const newFilename = `${this._videoBaseFilename}-segment-${this._currentVideoSegment}`;
|
||||
|
||||
// Restart recording with new segment filename
|
||||
this.setVideoRecording(this._videoRecordingConfig, newFilename);
|
||||
|
||||
return segmentPaths;
|
||||
}
|
||||
|
||||
// Request Interception and Traffic Analysis
|
||||
|
||||
/**
|
||||
|
||||
@ -31,9 +31,15 @@ const navigate = defineTool({
|
||||
},
|
||||
|
||||
handle: async (context, params, response) => {
|
||||
// Smart recording: Begin action
|
||||
await context.beginVideoAction('navigate');
|
||||
|
||||
const tab = await context.ensureTab();
|
||||
await tab.navigate(params.url);
|
||||
|
||||
// Smart recording: End action (auto-pause in smart mode)
|
||||
await context.endVideoAction('navigate');
|
||||
|
||||
response.setIncludeSnapshot();
|
||||
response.addCode(`// Navigate to ${params.url}`);
|
||||
response.addCode(`await page.goto('${params.url}');`);
|
||||
|
||||
@ -25,13 +25,14 @@ const startRecording = defineTool({
|
||||
schema: {
|
||||
name: 'browser_start_recording',
|
||||
title: 'Start video recording',
|
||||
description: 'Start recording browser session video. This must be called BEFORE performing browser actions you want to record. New browser contexts will be created with video recording enabled. Videos are automatically saved when pages/contexts close.',
|
||||
description: 'Start recording browser session video with intelligent viewport matching. For best results, the browser viewport size should match the video recording size to avoid gray space around content. Use browser_configure to set viewport size before recording.',
|
||||
inputSchema: z.object({
|
||||
size: z.object({
|
||||
width: z.number().optional().describe('Video width in pixels (default: scales to fit 800x800)'),
|
||||
height: z.number().optional().describe('Video height in pixels (default: scales to fit 800x800)'),
|
||||
}).optional().describe('Video recording size'),
|
||||
width: z.number().optional().describe('Video width in pixels (default: 1280). For full-frame content, set browser viewport to match this width.'),
|
||||
height: z.number().optional().describe('Video height in pixels (default: 720). For full-frame content, set browser viewport to match this height.'),
|
||||
}).optional().describe('Video recording dimensions. IMPORTANT: Browser viewport should match these dimensions to avoid gray borders around content.'),
|
||||
filename: z.string().optional().describe('Base filename for video files (default: session-{timestamp}.webm)'),
|
||||
autoSetViewport: z.boolean().optional().default(true).describe('Automatically set browser viewport to match video recording size (recommended for full-frame content)'),
|
||||
}),
|
||||
type: 'destructive',
|
||||
},
|
||||
@ -51,24 +52,72 @@ const startRecording = defineTool({
|
||||
videoDir = path.join(context.config.outputDir, 'videos');
|
||||
|
||||
|
||||
// Default video size for better demos
|
||||
const videoSize = params.size || { width: 1280, height: 720 };
|
||||
|
||||
// Update context options to enable video recording
|
||||
const recordVideoOptions: any = {
|
||||
dir: videoDir,
|
||||
size: videoSize,
|
||||
};
|
||||
|
||||
if (params.size)
|
||||
recordVideoOptions.size = params.size;
|
||||
|
||||
// Automatically set viewport to match video size for full-frame content
|
||||
if (params.autoSetViewport !== false) {
|
||||
try {
|
||||
await context.updateBrowserConfig({
|
||||
viewport: {
|
||||
width: videoSize.width || 1280,
|
||||
height: videoSize.height || 720,
|
||||
},
|
||||
});
|
||||
response.addResult(`🖥️ Browser viewport automatically set to ${videoSize.width}x${videoSize.height} to match video size`);
|
||||
} catch (error) {
|
||||
response.addResult(`⚠️ Could not auto-set viewport: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
response.addResult(`💡 Manually set viewport with: browser_configure({viewport: {width: ${videoSize.width}, height: ${videoSize.height}}})`);
|
||||
}
|
||||
}
|
||||
|
||||
// Store video recording config in context for future browser contexts
|
||||
context.setVideoRecording(recordVideoOptions, baseFilename);
|
||||
|
||||
response.addResult(`✓ Video recording enabled. Videos will be saved to: ${videoDir}`);
|
||||
response.addResult(`✓ Video files will be named: ${baseFilename}-*.webm`);
|
||||
response.addResult(`\nNext steps:`);
|
||||
response.addResult(`🎬 Video recording started!`);
|
||||
response.addResult(`📁 Videos will be saved to: ${videoDir}`);
|
||||
response.addResult(`📝 Files will be named: ${baseFilename}-*.webm`);
|
||||
response.addResult(`📐 Video size: ${videoSize.width}x${videoSize.height}`);
|
||||
|
||||
// Show viewport matching info
|
||||
if (params.autoSetViewport !== false) {
|
||||
response.addResult(`🖼️ Browser viewport matched to video size for full-frame content`);
|
||||
} else {
|
||||
response.addResult(`⚠️ Viewport not automatically set - you may see gray borders around content`);
|
||||
response.addResult(`💡 For full-frame content, use: browser_configure({viewport: {width: ${videoSize.width}, height: ${videoSize.height}}})`);
|
||||
}
|
||||
|
||||
// Show current recording mode
|
||||
const recordingInfo = context.getVideoRecordingInfo();
|
||||
response.addResult(`🎯 Recording mode: ${recordingInfo.mode}`);
|
||||
|
||||
switch (recordingInfo.mode) {
|
||||
case 'smart':
|
||||
response.addResult(`🧠 Smart mode: Auto-pauses during waits, resumes during actions`);
|
||||
response.addResult(`💡 Perfect for creating clean demo videos with minimal dead time`);
|
||||
break;
|
||||
case 'continuous':
|
||||
response.addResult(`📹 Continuous mode: Recording everything without pauses`);
|
||||
break;
|
||||
case 'action-only':
|
||||
response.addResult(`⚡ Action-only mode: Only recording during browser interactions`);
|
||||
break;
|
||||
case 'segment':
|
||||
response.addResult(`🎞️ Segment mode: Creating separate files for each action sequence`);
|
||||
break;
|
||||
}
|
||||
|
||||
response.addResult(`\n📋 Next steps:`);
|
||||
response.addResult(`1. Navigate to pages and perform browser actions`);
|
||||
response.addResult(`2. Use browser_stop_recording when finished to save videos`);
|
||||
response.addResult(`3. Videos are automatically saved when pages close`);
|
||||
response.addResult(`3. Use browser_set_recording_mode to change behavior`);
|
||||
response.addResult(`4. Videos are automatically saved when pages close`);
|
||||
response.addCode(`// Video recording enabled for new browser contexts`);
|
||||
response.addCode(`const context = await browser.newContext({`);
|
||||
response.addCode(` recordVideo: {`);
|
||||
@ -87,7 +136,7 @@ const stopRecording = defineTool({
|
||||
schema: {
|
||||
name: 'browser_stop_recording',
|
||||
title: 'Stop video recording',
|
||||
description: 'Stop video recording and return the paths to recorded video files. This closes all active pages to ensure videos are properly saved. Call this when you want to finalize and access the recorded videos.',
|
||||
description: 'Finalize video recording session and return paths to all recorded video files (.webm format). Automatically closes browser pages to ensure videos are properly saved and available for use. Essential final step for completing video recording workflows and accessing demo files.',
|
||||
inputSchema: z.object({}),
|
||||
type: 'readOnly',
|
||||
},
|
||||
@ -159,6 +208,17 @@ const getRecordingStatus = defineTool({
|
||||
response.addResult(`📐 Video size: auto-scaled to fit 800x800`);
|
||||
|
||||
response.addResult(`🎬 Active recordings: ${recordingInfo.activeRecordings}`);
|
||||
response.addResult(`🎯 Recording mode: ${recordingInfo.mode}`);
|
||||
|
||||
if (recordingInfo.paused) {
|
||||
response.addResult(`⏸️ Status: PAUSED (${recordingInfo.pausedRecordings} recordings stored)`);
|
||||
} else {
|
||||
response.addResult(`▶️ Status: RECORDING`);
|
||||
}
|
||||
|
||||
if (recordingInfo.mode === 'segment') {
|
||||
response.addResult(`🎞️ Current segment: ${recordingInfo.currentSegment}`);
|
||||
}
|
||||
|
||||
// Show helpful path info for MCP clients
|
||||
const outputDir = recordingInfo.config?.dir;
|
||||
@ -309,9 +369,93 @@ const revealArtifactPaths = defineTool({
|
||||
},
|
||||
});
|
||||
|
||||
const pauseRecording = defineTool({
|
||||
capability: 'core',
|
||||
|
||||
schema: {
|
||||
name: 'browser_pause_recording',
|
||||
title: 'Pause video recording',
|
||||
description: 'Manually pause the current video recording to eliminate dead time between actions. Useful for creating professional demo videos. In smart recording mode, pausing happens automatically during waits. Use browser_resume_recording to continue recording.',
|
||||
inputSchema: z.object({}),
|
||||
type: 'destructive',
|
||||
},
|
||||
|
||||
handle: async (context, params, response) => {
|
||||
const result = await context.pauseVideoRecording();
|
||||
response.addResult(`⏸️ ${result.message}`);
|
||||
if (result.paused > 0) {
|
||||
response.addResult(`💡 Use browser_resume_recording to continue`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const resumeRecording = defineTool({
|
||||
capability: 'core',
|
||||
|
||||
schema: {
|
||||
name: 'browser_resume_recording',
|
||||
title: 'Resume video recording',
|
||||
description: 'Manually resume previously paused video recording. New video segments will capture subsequent browser actions. In smart recording mode, resuming happens automatically when browser actions begin. Useful for precise control over recording timing in demo videos.',
|
||||
inputSchema: z.object({}),
|
||||
type: 'destructive',
|
||||
},
|
||||
|
||||
handle: async (context, params, response) => {
|
||||
const result = await context.resumeVideoRecording();
|
||||
response.addResult(`▶️ ${result.message}`);
|
||||
},
|
||||
});
|
||||
|
||||
const setRecordingMode = defineTool({
|
||||
capability: 'core',
|
||||
|
||||
schema: {
|
||||
name: 'browser_set_recording_mode',
|
||||
title: 'Set video recording mode',
|
||||
description: 'Configure intelligent video recording behavior for professional demo videos. Choose from continuous recording, smart auto-pause/resume, action-only capture, or segmented recording. Smart mode is recommended for marketing demos as it eliminates dead time automatically.',
|
||||
inputSchema: z.object({
|
||||
mode: z.enum(['continuous', 'smart', 'action-only', 'segment']).describe('Video recording behavior mode:\n• continuous: Record everything continuously including waits (traditional behavior, may have dead time)\n• smart: Automatically pause during waits, resume during actions (RECOMMENDED for clean demo videos)\n• action-only: Only record during active browser interactions, minimal recording time\n• segment: Create separate video files for each action sequence (useful for splitting demos into clips)'),
|
||||
}),
|
||||
type: 'destructive',
|
||||
},
|
||||
|
||||
handle: async (context, params, response) => {
|
||||
context.setVideoRecordingMode(params.mode);
|
||||
|
||||
response.addResult(`🎬 Video recording mode set to: ${params.mode}`);
|
||||
|
||||
switch (params.mode) {
|
||||
case 'continuous':
|
||||
response.addResult('📹 Will record everything continuously (traditional behavior)');
|
||||
break;
|
||||
case 'smart':
|
||||
response.addResult('🧠 Will auto-pause during waits, resume during actions (best for demos)');
|
||||
response.addResult('💡 Perfect for creating clean marketing/demo videos');
|
||||
break;
|
||||
case 'action-only':
|
||||
response.addResult('⚡ Will only record during active browser interactions');
|
||||
response.addResult('💡 Minimal recording time, focuses on user actions');
|
||||
break;
|
||||
case 'segment':
|
||||
response.addResult('🎞️ Will create separate video files for each action sequence');
|
||||
response.addResult('💡 Useful for breaking demos into individual clips');
|
||||
break;
|
||||
}
|
||||
|
||||
const recordingInfo = context.getVideoRecordingInfo();
|
||||
if (recordingInfo.enabled) {
|
||||
response.addResult(`\n🎥 Current recording status: ${recordingInfo.paused ? 'paused' : 'active'}`);
|
||||
response.addResult(`📊 Active recordings: ${recordingInfo.activeRecordings}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default [
|
||||
startRecording,
|
||||
stopRecording,
|
||||
getRecordingStatus,
|
||||
revealArtifactPaths,
|
||||
pauseRecording,
|
||||
resumeRecording,
|
||||
setRecordingMode,
|
||||
];
|
||||
|
||||
@ -23,11 +23,12 @@ const wait = defineTool({
|
||||
schema: {
|
||||
name: 'browser_wait_for',
|
||||
title: 'Wait for',
|
||||
description: 'Wait for text to appear or disappear or a specified time to pass. Returns page snapshot after waiting (configurable via browser_configure_snapshots).',
|
||||
description: 'Wait for text to appear or disappear or a specified time to pass. In smart recording mode, video recording is automatically paused during waits unless recordDuringWait is true.',
|
||||
inputSchema: z.object({
|
||||
time: z.number().optional().describe('The time to wait in seconds'),
|
||||
text: z.string().optional().describe('The text to wait for'),
|
||||
textGone: z.string().optional().describe('The text to wait for to disappear'),
|
||||
recordDuringWait: z.boolean().optional().default(false).describe('Whether to keep video recording active during the wait (default: false in smart mode, true in continuous mode)'),
|
||||
}),
|
||||
type: 'readOnly',
|
||||
},
|
||||
@ -36,6 +37,17 @@ const wait = defineTool({
|
||||
if (!params.text && !params.textGone && !params.time)
|
||||
throw new Error('Either time, text or textGone must be provided');
|
||||
|
||||
// Handle smart recording for waits
|
||||
const recordingInfo = context.getVideoRecordingInfo();
|
||||
const shouldPauseDuringWait = recordingInfo.enabled &&
|
||||
recordingInfo.mode !== 'continuous' &&
|
||||
!params.recordDuringWait;
|
||||
|
||||
if (shouldPauseDuringWait) {
|
||||
await context.endVideoAction('wait', true); // Pause recording for wait
|
||||
response.addResult(`⏸️ Video recording paused during wait (mode: ${recordingInfo.mode})`);
|
||||
}
|
||||
|
||||
const code: string[] = [];
|
||||
|
||||
if (params.time) {
|
||||
@ -57,7 +69,16 @@ const wait = defineTool({
|
||||
await locator.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
// Resume recording after wait if we paused it
|
||||
if (shouldPauseDuringWait) {
|
||||
await context.beginVideoAction('post-wait'); // Resume recording after wait
|
||||
response.addResult(`▶️ Video recording resumed after wait`);
|
||||
}
|
||||
|
||||
response.addResult(`Waited for ${params.text || params.textGone || params.time}`);
|
||||
if (params.recordDuringWait && recordingInfo.enabled) {
|
||||
response.addResult(`🎥 Video recording continued during wait`);
|
||||
}
|
||||
response.setIncludeSnapshot();
|
||||
},
|
||||
});
|
||||
|
||||
129
test-smart-recording.js
Executable file
129
test-smart-recording.js
Executable file
@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Smart Video Recording Test Script
|
||||
*
|
||||
* Tests the new smart recording features:
|
||||
* - Recording modes (continuous, smart, action-only, segment)
|
||||
* - Auto-pause/resume during waits
|
||||
* - Manual pause/resume controls
|
||||
* - Action-aware recording
|
||||
*/
|
||||
|
||||
console.log('🎬 Smart Video Recording Test');
|
||||
console.log('============================\n');
|
||||
|
||||
const testWorkflow = `
|
||||
# Smart Recording Test Workflow
|
||||
|
||||
## Test 1: Smart Mode (Default)
|
||||
1. browser_start_recording() # Should start in smart mode
|
||||
2. browser_recording_status() # Check mode and status
|
||||
3. browser_navigate({url: "https://example.com"}) # Should auto-resume for action
|
||||
4. browser_wait_for({time: 3}) # Should auto-pause during wait
|
||||
5. browser_recording_status() # Should show paused
|
||||
6. browser_click(...some element...) # Should auto-resume for action
|
||||
7. browser_wait_for({time: 2, recordDuringWait: true}) # Should keep recording
|
||||
8. browser_stop_recording() # Finalize and show video paths
|
||||
|
||||
## Test 2: Recording Mode Changes
|
||||
1. browser_set_recording_mode({mode: "continuous"}) # Switch to continuous
|
||||
2. browser_start_recording() # Start continuous recording
|
||||
3. browser_navigate({url: "https://httpbin.org"}) # Should not pause
|
||||
4. browser_wait_for({time: 3}) # Should not pause
|
||||
5. browser_set_recording_mode({mode: "segment"}) # Switch to segment mode
|
||||
6. browser_click(...some element...) # Should create new segment
|
||||
7. browser_stop_recording() # Show all segments
|
||||
|
||||
## Test 3: Manual Controls
|
||||
1. browser_start_recording() # Start recording
|
||||
2. browser_navigate({url: "https://example.com"}) # Action
|
||||
3. browser_pause_recording() # Manual pause
|
||||
4. browser_wait_for({time: 5}) # Long wait (no recording)
|
||||
5. browser_resume_recording() # Manual resume
|
||||
6. browser_click(...some element...) # Action should record
|
||||
7. browser_stop_recording() # Finalize
|
||||
|
||||
## Test 4: Path Resolution
|
||||
1. browser_reveal_artifact_paths() # Show where videos are stored
|
||||
2. browser_start_recording() # Start recording
|
||||
3. browser_recording_status() # Show current paths and status
|
||||
4. Perform some actions...
|
||||
5. browser_stop_recording() # Get actual video file paths
|
||||
|
||||
## Expected Results:
|
||||
✅ Smart mode auto-pauses during waits, resumes during actions
|
||||
✅ Continuous mode never pauses
|
||||
✅ Segment mode creates separate files per action sequence
|
||||
✅ Manual pause/resume works independently of mode
|
||||
✅ recordDuringWait parameter overrides smart mode behavior
|
||||
✅ Status tool shows current mode and pause state
|
||||
✅ Actual video files are created at reported paths
|
||||
`;
|
||||
|
||||
console.log(testWorkflow);
|
||||
|
||||
console.log('🎯 Key Features to Test:');
|
||||
console.log('========================');
|
||||
console.log('1. 📊 browser_set_recording_mode - Choose recording behavior');
|
||||
console.log('2. ⏸️ browser_pause_recording - Manual pause control');
|
||||
console.log('3. ▶️ browser_resume_recording - Manual resume control');
|
||||
console.log('4. 🧠 Smart wait handling - browser_wait_for with recordDuringWait');
|
||||
console.log('5. 📈 Enhanced status - browser_recording_status shows mode/state');
|
||||
console.log('6. 🗂️ Path discovery - browser_reveal_artifact_paths shows locations');
|
||||
console.log('7. 🎞️ Segment mode - Creates separate files per action sequence');
|
||||
console.log('');
|
||||
|
||||
console.log('💡 LLM-Friendly Usage Patterns:');
|
||||
console.log('===============================');
|
||||
console.log('');
|
||||
console.log('🎬 For Clean Demo Videos (Recommended):');
|
||||
console.log('```');
|
||||
console.log('browser_set_recording_mode({mode: "smart"}) // Auto-pause during waits');
|
||||
console.log('browser_start_recording()');
|
||||
console.log('// Perform demo actions - recording auto-manages pausing');
|
||||
console.log('browser_navigate(...), browser_click(...), etc.');
|
||||
console.log('browser_wait_for({time: 5}) // Auto-pauses here');
|
||||
console.log('browser_stop_recording() // Clean video with minimal dead time');
|
||||
console.log('```');
|
||||
console.log('');
|
||||
|
||||
console.log('🎞️ For Action Sequences:');
|
||||
console.log('```');
|
||||
console.log('browser_set_recording_mode({mode: "segment"}) // Separate file per action');
|
||||
console.log('browser_start_recording()');
|
||||
console.log('browser_navigate(...) // Creates segment-1.webm');
|
||||
console.log('browser_click(...) // Creates segment-2.webm');
|
||||
console.log('browser_type(...) // Creates segment-3.webm');
|
||||
console.log('browser_stop_recording() // Returns array of segment paths');
|
||||
console.log('```');
|
||||
console.log('');
|
||||
|
||||
console.log('⚡ For Minimal Recording:');
|
||||
console.log('```');
|
||||
console.log('browser_set_recording_mode({mode: "action-only"}) // Only record interactions');
|
||||
console.log('browser_start_recording()');
|
||||
console.log('// Automatically pauses between actions, resumes during interactions');
|
||||
console.log('browser_stop_recording()');
|
||||
console.log('```');
|
||||
console.log('');
|
||||
|
||||
console.log('📹 For Traditional Behavior:');
|
||||
console.log('```');
|
||||
console.log('browser_set_recording_mode({mode: "continuous"}) // Never auto-pause');
|
||||
console.log('browser_start_recording()');
|
||||
console.log('// Records everything including waits (original behavior)');
|
||||
console.log('browser_stop_recording()');
|
||||
console.log('```');
|
||||
console.log('');
|
||||
|
||||
console.log('🔧 Manual Control When Needed:');
|
||||
console.log('```');
|
||||
console.log('browser_start_recording()');
|
||||
console.log('browser_navigate(...)');
|
||||
console.log('browser_pause_recording() // Manual pause before long wait');
|
||||
console.log('// ... long processing or thinking time ...');
|
||||
console.log('browser_resume_recording() // Manual resume before next action');
|
||||
console.log('browser_click(...)');
|
||||
console.log('browser_stop_recording()');
|
||||
console.log('```');
|
||||
133
test-viewport-matching.js
Executable file
133
test-viewport-matching.js
Executable file
@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Video Recording Viewport Matching Test
|
||||
*
|
||||
* Demonstrates the solution to the gray border issue by ensuring
|
||||
* browser viewport matches video recording dimensions.
|
||||
*/
|
||||
|
||||
console.log('🎬 Video Recording Viewport Matching Test');
|
||||
console.log('==========================================\n');
|
||||
|
||||
console.log('🎯 THE PROBLEM:');
|
||||
console.log('===============');
|
||||
console.log('When video recording size ≠ browser viewport size:');
|
||||
console.log('📹 Video Canvas: 1280x720 (recording area)');
|
||||
console.log('🌐 Browser Viewport: 800x600 (content area)');
|
||||
console.log('🎥 Result: Small browser window in large video = GRAY BORDERS ❌');
|
||||
console.log('');
|
||||
|
||||
console.log('✅ THE SOLUTION:');
|
||||
console.log('================');
|
||||
console.log('Match browser viewport to video recording size:');
|
||||
console.log('📹 Video Canvas: 1280x720 (recording area)');
|
||||
console.log('🌐 Browser Viewport: 1280x720 (content area) ✅');
|
||||
console.log('🎥 Result: Browser content fills entire video = NO GRAY BORDERS ✅');
|
||||
console.log('');
|
||||
|
||||
console.log('🛠️ IMPLEMENTATION:');
|
||||
console.log('==================');
|
||||
console.log('');
|
||||
|
||||
console.log('### Method 1: Automatic Viewport Matching (RECOMMENDED)');
|
||||
console.log('```javascript');
|
||||
console.log('// This automatically sets viewport to match video size');
|
||||
console.log('browser_start_recording({');
|
||||
console.log(' size: { width: 1280, height: 720 },');
|
||||
console.log(' autoSetViewport: true // Default: true');
|
||||
console.log('})');
|
||||
console.log('```');
|
||||
console.log('✅ Pros: Automatic, no extra steps, prevents gray borders');
|
||||
console.log('⚠️ Cons: Changes browser viewport (usually desired)');
|
||||
console.log('');
|
||||
|
||||
console.log('### Method 2: Manual Viewport Control');
|
||||
console.log('```javascript');
|
||||
console.log('// Set viewport manually before recording');
|
||||
console.log('browser_configure({');
|
||||
console.log(' viewport: { width: 1280, height: 720 }');
|
||||
console.log('})');
|
||||
console.log('browser_start_recording({');
|
||||
console.log(' size: { width: 1280, height: 720 },');
|
||||
console.log(' autoSetViewport: false');
|
||||
console.log('})');
|
||||
console.log('```');
|
||||
console.log('✅ Pros: Full control over timing');
|
||||
console.log('⚠️ Cons: Extra step, must remember to match sizes');
|
||||
console.log('');
|
||||
|
||||
console.log('### Method 3: Disable Auto-Matching (NOT RECOMMENDED)');
|
||||
console.log('```javascript');
|
||||
console.log('// This will likely produce gray borders');
|
||||
console.log('browser_start_recording({');
|
||||
console.log(' size: { width: 1280, height: 720 },');
|
||||
console.log(' autoSetViewport: false');
|
||||
console.log('})');
|
||||
console.log('// Browser keeps current viewport (e.g., 800x600)');
|
||||
console.log('// Result: 800x600 browser in 1280x720 video = gray borders');
|
||||
console.log('```');
|
||||
console.log('❌ Produces gray borders around content');
|
||||
console.log('');
|
||||
|
||||
console.log('📐 RECOMMENDED VIDEO SIZES:');
|
||||
console.log('============================');
|
||||
console.log('For Marketing/Demo Videos:');
|
||||
console.log('• 1280x720 (HD 720p) - Most common, great balance of quality/size');
|
||||
console.log('• 1920x1080 (Full HD) - Higher quality, larger files');
|
||||
console.log('• 1024x768 (4:3) - Good for web applications, older projectors');
|
||||
console.log('');
|
||||
console.log('For Mobile Testing:');
|
||||
console.log('• 375x667 (iPhone portrait)');
|
||||
console.log('• 768x1024 (iPad portrait)');
|
||||
console.log('• 1024x768 (iPad landscape)');
|
||||
console.log('');
|
||||
console.log('For Desktop Applications:');
|
||||
console.log('• 1440x900 (Ultrawide)');
|
||||
console.log('• 1600x1200 (Large desktop)');
|
||||
console.log('');
|
||||
|
||||
console.log('🎬 PERFECT INTERNACHI DEMO SETUP:');
|
||||
console.log('==================================');
|
||||
console.log('```javascript');
|
||||
console.log('// 1. Set smart mode for clean videos');
|
||||
console.log('browser_set_recording_mode({ mode: "smart" })');
|
||||
console.log('');
|
||||
console.log('// 2. Start recording with auto-viewport matching');
|
||||
console.log('browser_start_recording({');
|
||||
console.log(' size: { width: 1280, height: 720 }, // HD quality');
|
||||
console.log(' filename: "internachi-expert-agent-demo",');
|
||||
console.log(' autoSetViewport: true // Prevents gray borders');
|
||||
console.log('})');
|
||||
console.log('');
|
||||
console.log('// 3. Browser viewport is now 1280x720 (matches video)');
|
||||
console.log('// 4. Perform demo actions - content fills entire video');
|
||||
console.log('browser_navigate({ url: "https://l.inspect.pics" })');
|
||||
console.log('browser_click({ element: "login", ref: "..." })');
|
||||
console.log('browser_type({ text: "demo@internachi.org", ... })');
|
||||
console.log('');
|
||||
console.log('// 5. Get clean video with no gray borders');
|
||||
console.log('const videos = browser_stop_recording()');
|
||||
console.log('```');
|
||||
console.log('');
|
||||
|
||||
console.log('🔧 DIAGNOSTIC TOOLS:');
|
||||
console.log('====================');
|
||||
console.log('Use these to verify your setup:');
|
||||
console.log('');
|
||||
console.log('• browser_recording_status() # Check video size and viewport');
|
||||
console.log('• browser_reveal_artifact_paths() # Find where videos are saved');
|
||||
console.log('• browser_take_screenshot() # Compare to video dimensions');
|
||||
console.log('• browser_configure() # Manually set viewport if needed');
|
||||
console.log('');
|
||||
|
||||
console.log('✅ KEY TAKEAWAYS:');
|
||||
console.log('=================');
|
||||
console.log('1. 🎯 ALWAYS match browser viewport to video recording size');
|
||||
console.log('2. 🤖 Use autoSetViewport: true (default) for automatic matching');
|
||||
console.log('3. 📐 Choose appropriate video size for your content (1280x720 recommended)');
|
||||
console.log('4. 🧠 Use smart recording mode for professional demo videos');
|
||||
console.log('5. 🔍 Use diagnostic tools to verify your setup');
|
||||
console.log('');
|
||||
console.log('Following these practices will eliminate gray borders and create');
|
||||
console.log('professional-quality demo videos where content fills the entire frame! 🎥✨');
|
||||
218
video-recording-best-practices.md
Normal file
218
video-recording-best-practices.md
Normal file
@ -0,0 +1,218 @@
|
||||
# 🎬 Video Recording Best Practices Guide
|
||||
|
||||
## 🖼️ Viewport and Video Size Matching
|
||||
|
||||
### The Gray Border Problem
|
||||
When video recording size doesn't match browser viewport size, you get **gray space around browser content** in the final video. This happens because:
|
||||
|
||||
- **Video Canvas**: The recording area (e.g., 1280x720)
|
||||
- **Browser Viewport**: The actual browser content area (e.g., 800x600)
|
||||
- **Result**: Small browser window inside larger video canvas = gray borders
|
||||
|
||||
### ✅ Solution: Match Viewport to Video Size
|
||||
|
||||
```javascript
|
||||
// RECOMMENDED: Automatic viewport matching (default behavior)
|
||||
browser_start_recording({
|
||||
size: { width: 1280, height: 720 },
|
||||
autoSetViewport: true // Default: true
|
||||
})
|
||||
|
||||
// MANUAL: Set viewport separately if needed
|
||||
browser_configure({
|
||||
viewport: { width: 1280, height: 720 }
|
||||
})
|
||||
browser_start_recording({
|
||||
size: { width: 1280, height: 720 },
|
||||
autoSetViewport: false
|
||||
})
|
||||
```
|
||||
|
||||
## 📐 Recommended Video Sizes
|
||||
|
||||
### For Marketing/Demo Videos:
|
||||
- **1280x720 (HD 720p)** - Most common, great for demos
|
||||
- **1920x1080 (Full HD)** - Higher quality, larger files
|
||||
- **1024x768 (4:3)** - Good for web applications
|
||||
|
||||
### For Mobile/Responsive Testing:
|
||||
- **375x667 (iPhone)** - Mobile portrait
|
||||
- **768x1024 (iPad)** - Tablet portrait
|
||||
- **1024x768 (iPad Landscape)** - Tablet landscape
|
||||
|
||||
### For Ultrawide/Desktop Apps:
|
||||
- **1440x900** - Ultrawide format
|
||||
- **1600x1200** - Large desktop format
|
||||
|
||||
## 🎯 Recording Mode Guide
|
||||
|
||||
### Smart Mode (Recommended for Demos)
|
||||
```javascript
|
||||
browser_set_recording_mode({ mode: "smart" })
|
||||
browser_start_recording()
|
||||
// Auto-pauses during waits, resumes during actions
|
||||
// Perfect for clean marketing videos
|
||||
```
|
||||
|
||||
### Continuous Mode (Traditional)
|
||||
```javascript
|
||||
browser_set_recording_mode({ mode: "continuous" })
|
||||
browser_start_recording()
|
||||
// Records everything including waits
|
||||
// May have dead time, but captures everything
|
||||
```
|
||||
|
||||
### Action-Only Mode (Minimal)
|
||||
```javascript
|
||||
browser_set_recording_mode({ mode: "action-only" })
|
||||
browser_start_recording()
|
||||
// Only records during browser interactions
|
||||
// Very focused, minimal file sizes
|
||||
```
|
||||
|
||||
### Segment Mode (Individual Clips)
|
||||
```javascript
|
||||
browser_set_recording_mode({ mode: "segment" })
|
||||
browser_start_recording()
|
||||
// Creates separate video files for each action
|
||||
// Great for breaking demos into clips
|
||||
```
|
||||
|
||||
## 🛠️ Complete Demo Recording Workflow
|
||||
|
||||
### Perfect Marketing Demo Setup:
|
||||
```javascript
|
||||
// 1. Set smart mode for auto-pause/resume
|
||||
browser_set_recording_mode({ mode: "smart" })
|
||||
|
||||
// 2. Start recording with optimal size (auto-sets viewport)
|
||||
browser_start_recording({
|
||||
size: { width: 1280, height: 720 },
|
||||
filename: "internachi-demo"
|
||||
})
|
||||
|
||||
// 3. Perform demo actions (recording manages itself)
|
||||
browser_navigate({ url: "https://l.inspect.pics" })
|
||||
browser_click({ element: "login button", ref: "..." })
|
||||
browser_type({ element: "email field", ref: "...", text: "demo@internachi.org" })
|
||||
browser_wait_for({ time: 3 }) // Auto-pauses here
|
||||
browser_click({ element: "submit", ref: "..." })
|
||||
|
||||
// 4. Finalize recording
|
||||
const videos = browser_stop_recording()
|
||||
// Returns: ["path/to/internachi-demo-segment1.webm"]
|
||||
```
|
||||
|
||||
### Multiple Segment Workflow:
|
||||
```javascript
|
||||
// Create separate clips for each feature
|
||||
browser_set_recording_mode({ mode: "segment" })
|
||||
browser_start_recording({ filename: "feature-demo" })
|
||||
|
||||
browser_navigate(...) // Creates feature-demo-segment-1.webm
|
||||
browser_click(...) // Creates feature-demo-segment-2.webm
|
||||
browser_type(...) // Creates feature-demo-segment-3.webm
|
||||
|
||||
const segments = browser_stop_recording()
|
||||
// Returns: ["segment-1.webm", "segment-2.webm", "segment-3.webm"]
|
||||
```
|
||||
|
||||
## 🎨 Visual Quality Tips
|
||||
|
||||
### 1. Always Match Viewport to Video Size
|
||||
- Use `autoSetViewport: true` (default) in `browser_start_recording`
|
||||
- Or manually set with `browser_configure({ viewport: {...} })`
|
||||
|
||||
### 2. Choose Appropriate Video Size
|
||||
- **1280x720** for most demos (HD quality, reasonable file size)
|
||||
- **1920x1080** for high-quality presentations
|
||||
- **1024x768** for web app demos (good for older projectors)
|
||||
|
||||
### 3. Consider Your Content
|
||||
- **Wide layouts**: Use 16:9 aspect ratio (1280x720, 1920x1080)
|
||||
- **Square content**: Use 1:1 or 4:3 ratios
|
||||
- **Mobile apps**: Use mobile device dimensions
|
||||
|
||||
### 4. Test Your Setup
|
||||
```javascript
|
||||
// Quick test workflow
|
||||
browser_reveal_artifact_paths() // See where videos will be saved
|
||||
browser_start_recording({ size: { width: 1280, height: 720 } })
|
||||
browser_navigate({ url: "https://example.com" })
|
||||
browser_take_screenshot() // Compare screenshot to video dimensions
|
||||
browser_stop_recording()
|
||||
```
|
||||
|
||||
## 🚀 Common Use Cases
|
||||
|
||||
### InterNACHI Marketing Demo:
|
||||
```javascript
|
||||
browser_set_recording_mode({ mode: "smart" })
|
||||
browser_start_recording({
|
||||
size: { width: 1280, height: 720 },
|
||||
filename: "internachi-expert-agent-demo"
|
||||
})
|
||||
// Perfect for marketing with auto-pause/resume
|
||||
```
|
||||
|
||||
### Feature Testing Documentation:
|
||||
```javascript
|
||||
browser_set_recording_mode({ mode: "segment" })
|
||||
browser_start_recording({
|
||||
size: { width: 1440, height: 900 },
|
||||
filename: "feature-test-clips"
|
||||
})
|
||||
// Creates individual clips for each test
|
||||
```
|
||||
|
||||
### Debugging Session:
|
||||
```javascript
|
||||
browser_set_recording_mode({ mode: "continuous" })
|
||||
browser_start_recording({
|
||||
size: { width: 1920, height: 1080 },
|
||||
filename: "debug-session"
|
||||
})
|
||||
// Records everything for later analysis
|
||||
```
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Problem: Gray borders around browser content
|
||||
**Solution**: Ensure viewport matches video size
|
||||
```javascript
|
||||
browser_start_recording({
|
||||
size: { width: 1280, height: 720 },
|
||||
autoSetViewport: true // This fixes it
|
||||
})
|
||||
```
|
||||
|
||||
### Problem: Video files not found
|
||||
**Solution**: Use path revelation tool
|
||||
```javascript
|
||||
browser_reveal_artifact_paths() // Shows exact file locations
|
||||
```
|
||||
|
||||
### Problem: Too much dead time in videos
|
||||
**Solution**: Use smart mode
|
||||
```javascript
|
||||
browser_set_recording_mode({ mode: "smart" }) // Auto-eliminates dead time
|
||||
```
|
||||
|
||||
### Problem: Need separate clips
|
||||
**Solution**: Use segment mode
|
||||
```javascript
|
||||
browser_set_recording_mode({ mode: "segment" }) // Creates individual files
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Quick Reference Commands
|
||||
|
||||
- `browser_start_recording()` - Begin recording with auto-viewport matching
|
||||
- `browser_set_recording_mode()` - Choose recording behavior
|
||||
- `browser_pause_recording()` - Manual pause control
|
||||
- `browser_resume_recording()` - Manual resume control
|
||||
- `browser_recording_status()` - Check current state
|
||||
- `browser_stop_recording()` - Finalize and get video paths
|
||||
- `browser_reveal_artifact_paths()` - Find where videos are saved
|
||||
- `browser_configure()` - Set viewport and other browser settings
|
||||
Loading…
x
Reference in New Issue
Block a user