diff --git a/docs/playwright-web/Recording-Videos.mdx b/docs/playwright-web/Recording-Videos.mdx new file mode 100644 index 0000000..20cf22b --- /dev/null +++ b/docs/playwright-web/Recording-Videos.mdx @@ -0,0 +1,95 @@ +# Recording Videos + +The MCP Playwright server supports recording videos of browser sessions. This feature is useful for debugging, documentation, and creating demos. + +## Starting Video Recording + +To start recording a video, use the `playwright_start_video` tool: + +```json +{ + "name": "playwright_start_video", + "parameters": { + "path": "/path/to/save/videos", // Optional - defaults to user's Videos folder + "width": 1280, // Optional - defaults to 1280 + "height": 720 // Optional - defaults to 720 + } +} +``` + +When you start video recording: + +1. The current browser session is closed +2. A new browser session with video recording enabled is created +3. If you were on a specific URL, the new session will navigate back to it + +## Stopping Video Recording + +To stop recording and save the video file, use the `playwright_stop_video` tool: + +```json +{ + "name": "playwright_stop_video", + "parameters": {} +} +``` + +When you stop video recording: + +1. The current browser context is closed, which automatically saves the video +2. A new browser context without video recording is created +3. If you were on a specific URL, the new session will navigate back to it + +## Video Format and Location + +- Videos are saved in the WebM format +- By default, videos are saved to the user's Videos folder +- Each page in a browser context creates its own video file +- Video files are named automatically with a timestamp +- The resolution is determined by the width and height parameters (default: 1280x720) + +## Example Usage + +Here's a complete example of starting a browser session, recording a video, and then stopping the recording: + +```json +// First, navigate to a website +{ + "name": "playwright_navigate", + "parameters": { + "url": "https://example.com" + } +} + +// Start video recording +{ + "name": "playwright_start_video", + "parameters": { + "path": "/my/videos/folder", + "width": 1920, + "height": 1080 + } +} + +// Perform some actions... +{ + "name": "playwright_click", + "parameters": { + "selector": "#my-button" + } +} + +// Stop recording +{ + "name": "playwright_stop_video", + "parameters": {} +} +``` + +## Technical Notes + +- Video recording uses Playwright's built-in video recording capabilities +- Starting a recording requires creating a new browser context +- Stopping a recording requires closing the browser context +- Video files are saved when the context is closed +- The feature works in headless and headful browser modes \ No newline at end of file diff --git a/docs/playwright-web/Supported-Tools.mdx b/docs/playwright-web/Supported-Tools.mdx new file mode 100644 index 0000000..361706f --- /dev/null +++ b/docs/playwright-web/Supported-Tools.mdx @@ -0,0 +1,65 @@ +# Supported Tools + +The Playwright MCP server provides the following tools: + +## Browser/Page Management + +- `playwright_navigate` - Navigate to a URL +- `playwright_close` - Close the browser and release all resources + +## Interactions + +- `playwright_click` - Click an element on the page +- `playwright_iframe_click` - Click an element in an iframe on the page +- `playwright_fill` - Fill out an input field +- `playwright_select` - Select an element on the page with Select tag +- `playwright_hover` - Hover an element on the page +- `playwright_drag` - Drag an element to a target location +- `playwright_press_key` - Press a keyboard key +- `playwright_click_and_switch_tab` - Click a link and switch to the newly opened tab + +## JavaScript Execution + +- `playwright_evaluate` - Execute JavaScript in the browser console +- `playwright_console_logs` - Retrieve console logs from the browser with filtering options + +## Response Handling + +- `playwright_expect_response` - Ask Playwright to start waiting for a HTTP response +- `playwright_assert_response` - Wait for and validate a previously initiated HTTP response wait operation + +## Browser Configuration + +- `playwright_custom_user_agent` - Set a custom User Agent for the browser + +## Page Content + +- `playwright_get_visible_text` - Get the visible text content of the current page +- `playwright_get_visible_html` - Get the HTML content of the current page + +## Navigation + +- `playwright_go_back` - Navigate back in browser history +- `playwright_go_forward` - Navigate forward in browser history + +## Media Capture + +- `playwright_screenshot` - Take a screenshot of the current page or a specific element +- `playwright_save_as_pdf` - Save the current page as a PDF file +- `playwright_start_video` - Start recording video of browser session +- `playwright_stop_video` - Stop video recording and save file + +## API Requests + +- `playwright_get` - Perform an HTTP GET request +- `playwright_post` - Perform an HTTP POST request +- `playwright_put` - Perform an HTTP PUT request +- `playwright_patch` - Perform an HTTP PATCH request +- `playwright_delete` - Perform an HTTP DELETE request + +## Code Generation + +- `start_codegen_session` - Start a new code generation session to record Playwright actions +- `end_codegen_session` - End a code generation session and generate the test file +- `get_codegen_session` - Get information about a code generation session +- `clear_codegen_session` - Clear a code generation session without generating a test \ No newline at end of file diff --git a/src/__tests__/tools/video.test.ts b/src/__tests__/tools/video.test.ts new file mode 100644 index 0000000..62fc90b --- /dev/null +++ b/src/__tests__/tools/video.test.ts @@ -0,0 +1,43 @@ +// Import the types +import { ToolContext } from '../../tools/common/types.js'; + +// Mocking the filesystem operations to avoid permission issues +jest.mock('fs', () => ({ + existsSync: jest.fn().mockReturnValue(true), + mkdirSync: jest.fn() +})); + +// Skip importing the actual tools until after mocks +let StartVideoRecordingTool; +let StopVideoRecordingTool; + +describe('Video Recording Tools', () => { + beforeAll(() => { + // Import tools after mocks are set up + const videoModule = require('../../tools/browser/video.js'); + StartVideoRecordingTool = videoModule.StartVideoRecordingTool; + StopVideoRecordingTool = videoModule.StopVideoRecordingTool; + }); + + test('StartVideoRecordingTool exists', () => { + expect(typeof StartVideoRecordingTool).toBe('function'); + }); + + test('StopVideoRecordingTool exists', () => { + expect(typeof StopVideoRecordingTool).toBe('function'); + }); + + test('StartVideoRecordingTool returns error if browser not initialized', async () => { + // Setup + const mockContextNoBrowser = { server: {} } as ToolContext; + const tool = new StartVideoRecordingTool({}); + const args = {}; + + // Execute + const result = await tool.execute(args, mockContextNoBrowser); + + // Assert + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain('Browser not initialized'); + }); +}); \ No newline at end of file diff --git a/src/toolHandler.ts b/src/toolHandler.ts index bdc18eb..388ffb9 100644 --- a/src/toolHandler.ts +++ b/src/toolHandler.ts @@ -17,7 +17,9 @@ import { ConsoleLogsTool, ExpectResponseTool, AssertResponseTool, - CustomUserAgentTool + CustomUserAgentTool, + StartVideoRecordingTool, + StopVideoRecordingTool } from './tools/browser/index.js'; import { ClickTool, @@ -96,6 +98,8 @@ let dragTool: DragTool; let pressKeyTool: PressKeyTool; let saveAsPdfTool: SaveAsPdfTool; let clickAndSwitchTabTool: ClickAndSwitchTabTool; +let startVideoRecordingTool: StartVideoRecordingTool; +let stopVideoRecordingTool: StopVideoRecordingTool; interface BrowserSettings { @@ -306,6 +310,8 @@ function initializeTools(server: any) { if (!pressKeyTool) pressKeyTool = new PressKeyTool(server); if (!saveAsPdfTool) saveAsPdfTool = new SaveAsPdfTool(server); if (!clickAndSwitchTabTool) clickAndSwitchTabTool = new ClickAndSwitchTabTool(server); + if (!startVideoRecordingTool) startVideoRecordingTool = new StartVideoRecordingTool(server); + if (!stopVideoRecordingTool) stopVideoRecordingTool = new StopVideoRecordingTool(server); } /** @@ -504,6 +510,12 @@ export async function handleToolCall( case "playwright_click_and_switch_tab": return await clickAndSwitchTabTool.execute(args, context); + case "playwright_start_video": + return await startVideoRecordingTool.execute(args, context); + + case "playwright_stop_video": + return await stopVideoRecordingTool.execute(args, context); + default: return { content: [{ diff --git a/src/tools.ts b/src/tools.ts index eef2cd3..cf576f2 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -3,6 +3,29 @@ import { codegenTools } from './tools/codegen'; export function createToolDefinitions() { return [ + // Video Recording tools + { + name: "playwright_start_video", + description: "Start recording video of browser session", + inputSchema: { + type: "object", + properties: { + path: { type: "string", description: "Directory path to save video file (default: user's Videos folder)" }, + width: { type: "number", description: "Video width in pixels (default: 1280)" }, + height: { type: "number", description: "Video height in pixels (default: 720)" } + }, + required: [] + } + }, + { + name: "playwright_stop_video", + description: "Stop video recording and save file", + inputSchema: { + type: "object", + properties: {}, + required: [] + } + }, // Codegen tools { name: "start_codegen_session", @@ -433,7 +456,9 @@ export const BROWSER_TOOLS = [ "playwright_drag", "playwright_press_key", "playwright_save_as_pdf", - "playwright_click_and_switch_tab" + "playwright_click_and_switch_tab", + "playwright_start_video", + "playwright_stop_video" ]; // API Request tools for conditional launch diff --git a/src/tools/browser/index.ts b/src/tools/browser/index.ts index 19cbb1f..0e71e5e 100644 --- a/src/tools/browser/index.ts +++ b/src/tools/browser/index.ts @@ -5,6 +5,7 @@ export * from './console.js'; export * from './interaction.js'; export * from './response.js'; export * from './useragent.js'; +export * from './video.js'; // TODO: Add exports for other browser tools as they are implemented // export * from './interaction.js'; \ No newline at end of file diff --git a/src/tools/browser/video.ts b/src/tools/browser/video.ts new file mode 100644 index 0000000..eb4831e --- /dev/null +++ b/src/tools/browser/video.ts @@ -0,0 +1,132 @@ +import * as path from 'node:path'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import type { Page, BrowserContext } from 'playwright'; +import { BrowserToolBase } from './base.js'; +import { ToolContext, ToolResponse, createSuccessResponse, createErrorResponse } from '../common/types.js'; + +const defaultVideosPath = path.join(os.homedir(), 'Videos'); + +/** + * Tool for recording videos of browser sessions + */ +export class StartVideoRecordingTool extends BrowserToolBase { + /** + * Execute the start video recording tool + */ + async execute(args: any, context: ToolContext): Promise { + if (!context.browser) { + return createErrorResponse("Browser not initialized!"); + } + + try { + // Ensure directory exists + const outputPath = args.path || defaultVideosPath; + const pathExists = fs.existsSync(outputPath); + if (!pathExists) { + fs.mkdirSync(outputPath, { recursive: true }); + } + + // Get current browser context + const browserContext = context.page?.context() as BrowserContext; + + // Check if video recording is already active + if ((browserContext as any)._options?.recordVideo) { + return createErrorResponse("Video recording is already active for this browser session."); + } + + // Close current page and context to start fresh with video recording + const url = context.page?.url() || 'about:blank'; + await context.page?.close(); + + // Create new context with video recording enabled + const newContext = await context.browser.newContext({ + recordVideo: { + dir: outputPath, + size: { + width: args.width || 1280, + height: args.height || 720 + } + }, + viewport: { + width: args.width || 1280, + height: args.height || 720 + } + }); + + // Create new page + const newPage = await newContext.newPage(); + + // If we had a URL, navigate back to it + if (url && url !== 'about:blank') { + await newPage.goto(url); + } + + // Store the new page in the global context + const { setGlobalPage } = await import('../../toolHandler.js'); + setGlobalPage(newPage); + + return createSuccessResponse([ + `Video recording started. Output will be saved to: ${outputPath}`, + `Browser viewport set to ${args.width || 1280}x${args.height || 720}` + ]); + } catch (error) { + return createErrorResponse(`Failed to start video recording: ${(error as Error).message}`); + } + } +} + +/** + * Tool for stopping video recording + */ +export class StopVideoRecordingTool extends BrowserToolBase { + /** + * Execute the stop video recording tool + */ + async execute(args: any, context: ToolContext): Promise { + return this.safeExecute(context, async (page) => { + try { + const browserContext = page.context(); + + // Check if video recording is active + if (!(browserContext as any)._options?.recordVideo) { + return createErrorResponse("No active video recording found."); + } + + // Video is saved automatically when context is closed + // Save current URL to restore it after + const url = page.url(); + + // Close current context which will save the video + await browserContext.close(); + + // Create new context without video recording + const newContext = await context.browser!.newContext({ + viewport: { + width: 1280, + height: 720 + } + }); + + // Create new page + const newPage = await newContext.newPage(); + + // Restore URL if needed + if (url && url !== 'about:blank') { + await newPage.goto(url); + } + + // Update global page reference + const { setGlobalPage } = await import('../../toolHandler.js'); + setGlobalPage(newPage); + + return createSuccessResponse([ + "Video recording stopped and saved successfully.", + "A new browser session has been started." + ]); + } catch (error) { + return createErrorResponse(`Failed to stop video recording: ${(error as Error).message}`); + } + }); + } +} \ No newline at end of file