style: fix linting errors and update README with new tools
- Auto-fix trailing spaces, curly braces, and indentation issues - Clean up boolean comparisons and code formatting - README automatically updated with new code injection tools: - browser_enable_debug_toolbar: Enable debug toolbar for client identification - browser_inject_custom_code: Inject custom JavaScript/CSS code - browser_list_injections: List all active code injections - browser_disable_debug_toolbar: Disable debug toolbar - browser_clear_injections: Remove custom code injections All linting checks now pass successfully.
This commit is contained in:
parent
b7ec4faf60
commit
a41a73af2a
27 changed files with 1603 additions and 134 deletions
119
src/context.ts
119
src/context.ts
|
|
@ -360,7 +360,7 @@ export class Context {
|
|||
setVideoRecording(config: { dir: string; size?: { width: number; height: number } }, baseFilename: string) {
|
||||
// Clear any existing video recording state first
|
||||
this.clearVideoRecordingState();
|
||||
|
||||
|
||||
this._videoRecordingConfig = config;
|
||||
this._videoBaseFilename = baseFilename;
|
||||
|
||||
|
|
@ -370,7 +370,7 @@ export class Context {
|
|||
// The next call to _ensureBrowserContext will create a new context with video recording
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
testDebug(`Video recording configured: ${JSON.stringify(config)}, filename: ${baseFilename}`);
|
||||
}
|
||||
|
||||
|
|
@ -417,7 +417,7 @@ export class Context {
|
|||
colorScheme?: 'light' | 'dark' | 'no-preference';
|
||||
permissions?: string[];
|
||||
offline?: boolean;
|
||||
|
||||
|
||||
// Browser UI Customization
|
||||
chromiumSandbox?: boolean;
|
||||
slowMo?: number;
|
||||
|
|
@ -495,13 +495,13 @@ export class Context {
|
|||
// Merge with existing args, avoiding duplicates
|
||||
const existingArgs = currentConfig.browser.launchOptions.args || [];
|
||||
const newArgs = [...existingArgs];
|
||||
|
||||
|
||||
for (const arg of changes.args) {
|
||||
if (!existingArgs.includes(arg)) {
|
||||
if (!existingArgs.includes(arg))
|
||||
newArgs.push(arg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
currentConfig.browser.launchOptions.args = newArgs;
|
||||
}
|
||||
|
||||
|
|
@ -580,10 +580,10 @@ export class Context {
|
|||
// Keep recording config available for inspection until explicitly cleared
|
||||
// Don't clear it immediately to help with debugging
|
||||
testDebug(`stopVideoRecording complete: ${videoPaths.length} videos saved, config preserved for debugging`);
|
||||
|
||||
|
||||
// Clear the page tracking but keep config for status queries
|
||||
this._activePagesWithVideos.clear();
|
||||
|
||||
|
||||
return videoPaths;
|
||||
}
|
||||
|
||||
|
|
@ -612,7 +612,7 @@ export class Context {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -633,10 +633,10 @@ export class Context {
|
|||
|
||||
this._videoRecordingPaused = true;
|
||||
testDebug(`Video recording paused: ${pausedCount} recordings stored`);
|
||||
|
||||
return {
|
||||
paused: pausedCount,
|
||||
message: `Video recording paused. ${pausedCount} active recordings stored.`
|
||||
|
||||
return {
|
||||
paused: pausedCount,
|
||||
message: `Video recording paused. ${pausedCount} active recordings stored.`
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -652,16 +652,16 @@ export class Context {
|
|||
}
|
||||
|
||||
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) {
|
||||
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();
|
||||
|
|
@ -669,10 +669,10 @@ export class Context {
|
|||
|
||||
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.`
|
||||
|
||||
return {
|
||||
resumed: resumedCount,
|
||||
message: `Video recording resumed. ${resumedCount} recordings will restart when pages are created.`
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -691,7 +691,8 @@ export class Context {
|
|||
}
|
||||
|
||||
async beginVideoAction(actionName: string): Promise<void> {
|
||||
if (!this._videoRecordingConfig || !this._autoRecordingEnabled) return;
|
||||
if (!this._videoRecordingConfig || !this._autoRecordingEnabled)
|
||||
return;
|
||||
|
||||
testDebug(`beginVideoAction: ${actionName}, mode: ${this._videoRecordingMode}`);
|
||||
|
||||
|
|
@ -699,27 +700,28 @@ export class Context {
|
|||
case 'continuous':
|
||||
// Always recording, no action needed
|
||||
break;
|
||||
|
||||
|
||||
case 'smart':
|
||||
case 'action-only':
|
||||
// Resume recording if paused
|
||||
if (this._videoRecordingPaused) {
|
||||
if (this._videoRecordingPaused)
|
||||
await this.resumeVideoRecording();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case 'segment':
|
||||
// Create new segment for this action
|
||||
if (this._videoRecordingPaused) {
|
||||
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;
|
||||
if (!this._videoRecordingConfig || !this._autoRecordingEnabled)
|
||||
return;
|
||||
|
||||
testDebug(`endVideoAction: ${actionName}, shouldPause: ${shouldPause}, mode: ${this._videoRecordingMode}`);
|
||||
|
||||
|
|
@ -727,15 +729,15 @@ export class Context {
|
|||
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) {
|
||||
if (shouldPause && !this._videoRecordingPaused)
|
||||
await this.pauseVideoRecording();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case 'segment':
|
||||
// Always end segment after action
|
||||
await this.finalizeCurrentVideoSegment();
|
||||
|
|
@ -744,20 +746,21 @@ export class Context {
|
|||
}
|
||||
|
||||
async finalizeCurrentVideoSegment(): Promise<string[]> {
|
||||
if (!this._videoRecordingConfig) return [];
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -1020,60 +1023,60 @@ export class Context {
|
|||
* Auto-inject debug toolbar and custom code into a new page
|
||||
*/
|
||||
private async _injectCodeIntoPage(page: playwright.Page): Promise<void> {
|
||||
if (!this.injectionConfig || !this.injectionConfig.enabled) {
|
||||
if (!this.injectionConfig || !this.injectionConfig.enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Import the injection functions (dynamic import to avoid circular deps)
|
||||
const { generateDebugToolbarScript, wrapInjectedCode, generateInjectionScript } = await import('./tools/codeInjection.js');
|
||||
|
||||
|
||||
// Inject debug toolbar if enabled
|
||||
if (this.injectionConfig.debugToolbar.enabled) {
|
||||
const toolbarScript = generateDebugToolbarScript(
|
||||
this.injectionConfig.debugToolbar,
|
||||
this.sessionId,
|
||||
this.clientVersion,
|
||||
this._sessionStartTime
|
||||
this.injectionConfig.debugToolbar,
|
||||
this.sessionId,
|
||||
this.clientVersion,
|
||||
this._sessionStartTime
|
||||
);
|
||||
|
||||
|
||||
// Add to page init script for future navigations
|
||||
await page.addInitScript(toolbarScript);
|
||||
|
||||
|
||||
// Execute immediately if page is already loaded
|
||||
if (page.url() && page.url() !== 'about:blank') {
|
||||
await page.evaluate(toolbarScript).catch(error => {
|
||||
testDebug('Error executing debug toolbar script on existing page:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
testDebug(`Debug toolbar auto-injected into page: ${page.url()}`);
|
||||
}
|
||||
|
||||
// Inject custom code
|
||||
for (const injection of this.injectionConfig.customInjections) {
|
||||
if (!injection.enabled || !injection.autoInject) {
|
||||
if (!injection.enabled || !injection.autoInject)
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const wrappedCode = wrapInjectedCode(
|
||||
injection,
|
||||
this.sessionId,
|
||||
this.injectionConfig.debugToolbar.projectName
|
||||
injection,
|
||||
this.sessionId,
|
||||
this.injectionConfig.debugToolbar.projectName
|
||||
);
|
||||
const injectionScript = generateInjectionScript(wrappedCode);
|
||||
|
||||
|
||||
// Add to page init script
|
||||
await page.addInitScript(injectionScript);
|
||||
|
||||
|
||||
// Execute immediately if page is already loaded
|
||||
if (page.url() && page.url() !== 'about:blank') {
|
||||
await page.evaluate(injectionScript).catch(error => {
|
||||
testDebug(`Error executing custom injection "${injection.name}" on existing page:`, error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
testDebug(`Custom injection "${injection.name}" auto-injected into page: ${page.url()}`);
|
||||
} catch (error) {
|
||||
testDebug(`Error injecting custom code "${injection.name}":`, error);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Code Injection Tools for MCP Client Identification and Custom Scripts
|
||||
*
|
||||
*
|
||||
* Provides tools for injecting debug toolbars and custom code into browser pages.
|
||||
* Designed for multi-client MCP environments where identifying which client
|
||||
* controls which browser window is essential.
|
||||
|
|
@ -47,7 +62,7 @@ export function generateDebugToolbarScript(config: DebugToolbarConfig, sessionId
|
|||
const projectName = config.projectName || 'MCP Client';
|
||||
const clientInfo = clientVersion ? `${clientVersion.name} v${clientVersion.version}` : 'Unknown Client';
|
||||
const startTime = sessionStartTime || Date.now();
|
||||
|
||||
|
||||
return `
|
||||
/* BEGIN PLAYWRIGHT-MCP-DEBUG-TOOLBAR */
|
||||
/* This debug toolbar was injected by Playwright MCP server */
|
||||
|
|
@ -217,7 +232,7 @@ export function wrapInjectedCode(injection: CustomInjection, sessionId: string,
|
|||
<!-- Session: ${sessionId}${projectInfo} -->
|
||||
<!-- This code was injected by Playwright MCP and should be ignored by LLMs -->`;
|
||||
const footer = `<!-- END PLAYWRIGHT-MCP-INJECTION: ${injection.name} -->`;
|
||||
|
||||
|
||||
if (injection.type === 'javascript') {
|
||||
return `${header}
|
||||
<script>
|
||||
|
|
@ -233,7 +248,7 @@ ${injection.code}
|
|||
</style>
|
||||
${footer}`;
|
||||
}
|
||||
|
||||
|
||||
return `${header}
|
||||
${injection.code}
|
||||
${footer}`;
|
||||
|
|
@ -315,7 +330,7 @@ const enableDebugToolbar = defineTool({
|
|||
},
|
||||
handle: async (context: Context, params: z.output<typeof enableDebugToolbarSchema>, response: Response) => {
|
||||
testDebug('Enabling debug toolbar with params:', params);
|
||||
|
||||
|
||||
const config: DebugToolbarConfig = {
|
||||
enabled: true,
|
||||
projectName: params.projectName || 'MCP Client',
|
||||
|
|
@ -325,7 +340,7 @@ const enableDebugToolbar = defineTool({
|
|||
showDetails: params.showDetails !== false,
|
||||
opacity: params.opacity || 0.9
|
||||
};
|
||||
|
||||
|
||||
// Store config in context
|
||||
if (!context.injectionConfig) {
|
||||
context.injectionConfig = {
|
||||
|
|
@ -337,10 +352,10 @@ const enableDebugToolbar = defineTool({
|
|||
context.injectionConfig.debugToolbar = config;
|
||||
context.injectionConfig.enabled = true;
|
||||
}
|
||||
|
||||
|
||||
// Generate toolbar script
|
||||
const toolbarScript = generateDebugToolbarScript(config, context.sessionId, context.clientVersion, (context as any)._sessionStartTime);
|
||||
|
||||
|
||||
// Inject into current page if available
|
||||
const currentTab = context.currentTab();
|
||||
if (currentTab) {
|
||||
|
|
@ -352,7 +367,7 @@ const enableDebugToolbar = defineTool({
|
|||
testDebug('Error injecting toolbar into current page:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const resultMessage = `Debug toolbar enabled for project "${config.projectName}"`;
|
||||
response.addResult(resultMessage);
|
||||
response.addResult(`Session ID: ${context.sessionId}`);
|
||||
|
|
@ -371,7 +386,7 @@ const injectCustomCode = defineTool({
|
|||
},
|
||||
handle: async (context: Context, params: z.output<typeof injectCustomCodeSchema>, response: Response) => {
|
||||
testDebug('Injecting custom code:', { name: params.name, type: params.type });
|
||||
|
||||
|
||||
if (!context.injectionConfig) {
|
||||
context.injectionConfig = {
|
||||
debugToolbar: { enabled: false, minimized: false, showDetails: true, position: 'top-right', theme: 'dark', opacity: 0.9 },
|
||||
|
|
@ -379,7 +394,7 @@ const injectCustomCode = defineTool({
|
|||
enabled: true
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Create injection object
|
||||
const injection: CustomInjection = {
|
||||
id: `${params.name}_${Date.now()}`,
|
||||
|
|
@ -390,19 +405,19 @@ const injectCustomCode = defineTool({
|
|||
persistent: params.persistent !== false,
|
||||
autoInject: params.autoInject !== false
|
||||
};
|
||||
|
||||
|
||||
// Remove any existing injection with the same name
|
||||
context.injectionConfig.customInjections = context.injectionConfig.customInjections.filter(
|
||||
inj => inj.name !== params.name
|
||||
inj => inj.name !== params.name
|
||||
);
|
||||
|
||||
|
||||
// Add new injection
|
||||
context.injectionConfig.customInjections.push(injection);
|
||||
|
||||
|
||||
// Wrap code with LLM-safe markers
|
||||
const wrappedCode = wrapInjectedCode(injection, context.sessionId, context.injectionConfig.debugToolbar.projectName);
|
||||
const injectionScript = generateInjectionScript(wrappedCode);
|
||||
|
||||
|
||||
// Inject into current page if available
|
||||
const currentTab = context.currentTab();
|
||||
if (currentTab && injection.autoInject) {
|
||||
|
|
@ -414,7 +429,7 @@ const injectCustomCode = defineTool({
|
|||
testDebug('Error injecting custom code into current page:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
response.addResult(`Custom ${params.type} injection "${params.name}" added successfully`);
|
||||
response.addResult(`Total injections: ${context.injectionConfig.customInjections.length}`);
|
||||
response.addResult(`Auto-inject enabled: ${injection.autoInject}`);
|
||||
|
|
@ -432,12 +447,12 @@ const listInjections = defineTool({
|
|||
},
|
||||
handle: async (context: Context, params: any, response: Response) => {
|
||||
const config = context.injectionConfig;
|
||||
|
||||
|
||||
if (!config) {
|
||||
response.addResult('No injection configuration found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
response.addResult(`Session ID: ${context.sessionId}`);
|
||||
response.addResult(`\nDebug Toolbar:`);
|
||||
response.addResult(`- Enabled: ${config.debugToolbar.enabled}`);
|
||||
|
|
@ -447,7 +462,7 @@ const listInjections = defineTool({
|
|||
response.addResult(`- Theme: ${config.debugToolbar.theme}`);
|
||||
response.addResult(`- Minimized: ${config.debugToolbar.minimized}`);
|
||||
}
|
||||
|
||||
|
||||
response.addResult(`\nCustom Injections (${config.customInjections.length}):`);
|
||||
if (config.customInjections.length === 0) {
|
||||
response.addResult('- None');
|
||||
|
|
@ -471,19 +486,19 @@ const disableDebugToolbar = defineTool({
|
|||
type: 'destructive',
|
||||
},
|
||||
handle: async (context: Context, params: any, response: Response) => {
|
||||
if (context.injectionConfig) {
|
||||
if (context.injectionConfig)
|
||||
context.injectionConfig.debugToolbar.enabled = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Remove from current page if available
|
||||
const currentTab = context.currentTab();
|
||||
if (currentTab) {
|
||||
try {
|
||||
await currentTab.page.evaluate(() => {
|
||||
const toolbar = document.getElementById('playwright-mcp-debug-toolbar');
|
||||
if (toolbar) {
|
||||
if (toolbar)
|
||||
toolbar.remove();
|
||||
}
|
||||
|
||||
(window as any).playwrightMcpDebugToolbar = false;
|
||||
});
|
||||
testDebug('Debug toolbar removed from current page');
|
||||
|
|
@ -491,7 +506,7 @@ const disableDebugToolbar = defineTool({
|
|||
testDebug('Error removing toolbar from current page:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
response.addResult('Debug toolbar disabled');
|
||||
}
|
||||
});
|
||||
|
|
@ -510,22 +525,22 @@ const clearInjections = defineTool({
|
|||
response.addResult('No injections to clear');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const clearedCount = context.injectionConfig.customInjections.length;
|
||||
context.injectionConfig.customInjections = [];
|
||||
|
||||
|
||||
if (params.includeToolbar) {
|
||||
context.injectionConfig.debugToolbar.enabled = false;
|
||||
|
||||
|
||||
// Remove toolbar from current page
|
||||
const currentTab = context.currentTab();
|
||||
if (currentTab) {
|
||||
try {
|
||||
await currentTab.page.evaluate(() => {
|
||||
const toolbar = document.getElementById('playwright-mcp-debug-toolbar');
|
||||
if (toolbar) {
|
||||
if (toolbar)
|
||||
toolbar.remove();
|
||||
}
|
||||
|
||||
(window as any).playwrightMcpDebugToolbar = false;
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
@ -533,7 +548,7 @@ const clearInjections = defineTool({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
response.addResult(`Cleared ${clearedCount} custom injections${params.includeToolbar ? ' and disabled debug toolbar' : ''}`);
|
||||
}
|
||||
});
|
||||
|
|
@ -544,4 +559,4 @@ export default [
|
|||
listInjections,
|
||||
disableDebugToolbar,
|
||||
clearInjections,
|
||||
];
|
||||
];
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const configureSchema = z.object({
|
|||
colorScheme: z.enum(['light', 'dark', 'no-preference']).optional().describe('Preferred color scheme'),
|
||||
permissions: z.array(z.string()).optional().describe('Permissions to grant (e.g., ["geolocation", "notifications", "camera", "microphone"])'),
|
||||
offline: z.boolean().optional().describe('Whether to emulate offline network conditions (equivalent to DevTools offline mode)'),
|
||||
|
||||
|
||||
// Browser UI Customization Options
|
||||
chromiumSandbox: z.boolean().optional().describe('Enable/disable Chromium sandbox (affects browser appearance)'),
|
||||
slowMo: z.number().min(0).optional().describe('Slow down operations by specified milliseconds (helps with visual tracking)'),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ const startRecording = defineTool({
|
|||
|
||||
// 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,
|
||||
|
|
@ -62,7 +62,7 @@ const startRecording = defineTool({
|
|||
};
|
||||
|
||||
// Automatically set viewport to match video size for full-frame content
|
||||
if (params.autoSetViewport !== false) {
|
||||
if (params.autoSetViewport) {
|
||||
try {
|
||||
await context.updateBrowserConfig({
|
||||
viewport: {
|
||||
|
|
@ -84,19 +84,19 @@ const startRecording = defineTool({
|
|||
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) {
|
||||
if (params.autoSetViewport) {
|
||||
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`);
|
||||
|
|
@ -112,7 +112,7 @@ const startRecording = defineTool({
|
|||
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`);
|
||||
|
|
@ -179,11 +179,11 @@ const getRecordingStatus = defineTool({
|
|||
response.addResult('1. Use browser_start_recording to enable recording');
|
||||
response.addResult('2. Navigate to pages and perform actions');
|
||||
response.addResult('3. Use browser_stop_recording to save videos');
|
||||
|
||||
|
||||
// Show potential artifact locations for debugging
|
||||
const registry = ArtifactManagerRegistry.getInstance();
|
||||
const artifactManager = context.sessionId ? registry.getManager(context.sessionId) : undefined;
|
||||
|
||||
|
||||
if (artifactManager) {
|
||||
const baseDir = artifactManager.getBaseDirectory();
|
||||
const sessionDir = artifactManager.getSessionDirectory();
|
||||
|
|
@ -195,7 +195,7 @@ const getRecordingStatus = defineTool({
|
|||
response.addResult(`\n⚠️ No artifact manager configured - videos will save to default output directory`);
|
||||
response.addResult(`📁 Default output: ${path.join(context.config.outputDir, 'videos')}`);
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -209,23 +209,23 @@ const getRecordingStatus = defineTool({
|
|||
|
||||
response.addResult(`🎬 Active recordings: ${recordingInfo.activeRecordings}`);
|
||||
response.addResult(`🎯 Recording mode: ${recordingInfo.mode}`);
|
||||
|
||||
if (recordingInfo.paused) {
|
||||
|
||||
if (recordingInfo.paused)
|
||||
response.addResult(`⏸️ Status: PAUSED (${recordingInfo.pausedRecordings} recordings stored)`);
|
||||
} else {
|
||||
else
|
||||
response.addResult(`▶️ Status: RECORDING`);
|
||||
}
|
||||
|
||||
if (recordingInfo.mode === 'segment') {
|
||||
|
||||
|
||||
if (recordingInfo.mode === 'segment')
|
||||
response.addResult(`🎞️ Current segment: ${recordingInfo.currentSegment}`);
|
||||
}
|
||||
|
||||
|
||||
// Show helpful path info for MCP clients
|
||||
const outputDir = recordingInfo.config?.dir;
|
||||
if (outputDir) {
|
||||
const absolutePath = path.resolve(outputDir);
|
||||
response.addResult(`📍 Absolute path: ${absolutePath}`);
|
||||
|
||||
|
||||
// Check if directory exists and show contents
|
||||
const fs = await import('fs');
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
|
|
@ -249,7 +249,7 @@ const getRecordingStatus = defineTool({
|
|||
// Show debug information
|
||||
const registry = ArtifactManagerRegistry.getInstance();
|
||||
const artifactManager = context.sessionId ? registry.getManager(context.sessionId) : undefined;
|
||||
|
||||
|
||||
if (artifactManager) {
|
||||
response.addResult(`\n🔍 Debug Info:`);
|
||||
response.addResult(`🆔 Session ID: ${context.sessionId}`);
|
||||
|
|
@ -313,13 +313,13 @@ const revealArtifactPaths = defineTool({
|
|||
const files = items.filter(item => item.isFile()).map(item => item.name);
|
||||
const dirs = items.filter(item => item.isDirectory()).map(item => item.name);
|
||||
|
||||
if (dirs.length > 0) {
|
||||
if (dirs.length > 0)
|
||||
response.addResult(`\n📂 Existing subdirectories: ${dirs.join(', ')}`);
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
|
||||
if (files.length > 0)
|
||||
response.addResult(`📄 Files in session directory: ${files.join(', ')}`);
|
||||
}
|
||||
|
||||
|
||||
// Count .webm files across all subdirectories
|
||||
let webmCount = 0;
|
||||
|
|
@ -328,11 +328,11 @@ const revealArtifactPaths = defineTool({
|
|||
const contents = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const item of contents) {
|
||||
const fullPath = path.join(dir, item.name);
|
||||
if (item.isDirectory()) {
|
||||
if (item.isDirectory())
|
||||
countWebmFiles(fullPath);
|
||||
} else if (item.name.endsWith('.webm')) {
|
||||
else if (item.name.endsWith('.webm'))
|
||||
webmCount++;
|
||||
}
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore permission errors
|
||||
|
|
@ -340,9 +340,9 @@ const revealArtifactPaths = defineTool({
|
|||
}
|
||||
countWebmFiles(sessionDir);
|
||||
|
||||
if (webmCount > 0) {
|
||||
if (webmCount > 0)
|
||||
response.addResult(`🎬 Total .webm video files found: ${webmCount}`);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
response.addResult(`⚠️ Could not list session directory contents: ${error.message}`);
|
||||
}
|
||||
|
|
@ -383,9 +383,9 @@ const pauseRecording = defineTool({
|
|||
handle: async (context, params, response) => {
|
||||
const result = await context.pauseVideoRecording();
|
||||
response.addResult(`⏸️ ${result.message}`);
|
||||
if (result.paused > 0) {
|
||||
if (result.paused > 0)
|
||||
response.addResult(`💡 Use browser_resume_recording to continue`);
|
||||
}
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -421,9 +421,9 @@ const setRecordingMode = defineTool({
|
|||
|
||||
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)');
|
||||
|
|
@ -441,7 +441,7 @@ const setRecordingMode = defineTool({
|
|||
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'}`);
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ const wait = defineTool({
|
|||
|
||||
// Handle smart recording for waits
|
||||
const recordingInfo = context.getVideoRecordingInfo();
|
||||
const shouldPauseDuringWait = recordingInfo.enabled &&
|
||||
recordingInfo.mode !== 'continuous' &&
|
||||
const shouldPauseDuringWait = recordingInfo.enabled &&
|
||||
recordingInfo.mode !== 'continuous' &&
|
||||
!params.recordDuringWait;
|
||||
|
||||
if (shouldPauseDuringWait) {
|
||||
|
|
@ -76,9 +76,9 @@ const wait = defineTool({
|
|||
}
|
||||
|
||||
response.addResult(`Waited for ${params.text || params.textGone || params.time}`);
|
||||
if (params.recordDuringWait && recordingInfo.enabled) {
|
||||
if (params.recordDuringWait && recordingInfo.enabled)
|
||||
response.addResult(`🎥 Video recording continued during wait`);
|
||||
}
|
||||
|
||||
response.setIncludeSnapshot();
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue