diff --git a/docs/docs/release.mdx b/docs/docs/release.mdx index d6034dd..e04766b 100644 --- a/docs/docs/release.mdx +++ b/docs/docs/release.mdx @@ -5,6 +5,9 @@ import YouTubeVideoEmbed from '@site/src/components/HomepageFeatures/YouTubeVide # Release Notes +## Version 0.2.9 +- Fixed Screenshot issue with Cline, Cursor and Windows 11 (Reported by @MackDing, @mengjian-github) + ## Version 0.2.8 - Support of iFrame while running Playwright test via MCP (Supports Cline as well). Thanks to @VinceOPS - Fixed issue while saving PNG file. Thanks to @BayLee4 diff --git a/package-lock.json b/package-lock.json index 7c94997..dc27ee5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@executeautomation/playwright-mcp-server", - "version": "0.2.8", + "version": "0.2.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@executeautomation/playwright-mcp-server", - "version": "0.2.8", + "version": "0.2.9", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "1.6.1", diff --git a/package.json b/package.json index e967724..df25cc7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@executeautomation/playwright-mcp-server", - "version": "0.2.8", + "version": "0.2.9", "description": "Model Context Protocol servers for Playwright", "license": "MIT", "author": "ExecuteAutomation, Ltd (https://executeautomation.com)", diff --git a/src/index.ts b/src/index.ts index 2973ee5..371c0e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ async function runServer() { const server = new Server( { name: "executeautomation/playwright-mcp-server", - version: "0.2.8", + version: "0.2.9", }, { capabilities: { diff --git a/src/toolsHandler.ts b/src/toolsHandler.ts index b26aa8f..aa3ba50 100644 --- a/src/toolsHandler.ts +++ b/src/toolsHandler.ts @@ -94,85 +94,88 @@ export async function handleToolCall( }; } - case "playwright_screenshot": { - try { - const screenshotOptions: any = { - type: args.type || "png", - fullPage: !!args.fullPage - }; - - if (args.selector) { - const element = await page!.$(args.selector); - if (!element) { - return { - content: [{ - type: "text", - text: `Element not found: ${args.selector}`, - }], - isError: true - }; + case "playwright_screenshot": { + try { + const screenshotOptions: any = { + type: args.type || "png", + fullPage: !!args.fullPage + }; + + if (args.selector) { + const element = await page!.$(args.selector); + if (!element) { + return { + content: [{ + type: "text", + text: `Element not found: ${args.selector}`, + }], + isError: true + }; + } + screenshotOptions.element = element; } - screenshotOptions.element = element; - } - - if (args.mask) { - screenshotOptions.mask = await Promise.all( - args.mask.map(async (selector: string) => await page!.$(selector)) - ); - } - - const screenshot = await page!.screenshot(screenshotOptions); - const base64Screenshot = screenshot.toString('base64'); - - const responseContent: (TextContent | ImageContent)[] = []; - - // Handle PNG file saving - if (args.savePng === true) { + + if (args.mask) { + screenshotOptions.mask = await Promise.all( + args.mask.map(async (selector: string) => await page!.$(selector)) + ); + } + + // Generate an output path const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const filename = `${args.name}-${timestamp}.png`; + const filename = `${args.name || 'screenshot'}-${timestamp}.png`; const downloadsDir = args.downloadsDir || defaultDownloadsPath; - + // Create downloads directory if it doesn't exist if (!fs.existsSync(downloadsDir)) { fs.mkdirSync(downloadsDir, { recursive: true }); } - - const filePath = path.join(downloadsDir, filename); - await fs.promises.writeFile(filePath, screenshot); + + const outputPath = path.join(downloadsDir, filename); + + // Add the path to the screenshot options + screenshotOptions.path = outputPath; + + // Take the screenshot with the path included + const screenshot = await page!.screenshot(screenshotOptions); + const base64Screenshot = screenshot.toString('base64'); + + const responseContent: TextContent[] = []; + + // Add relative path info to response + const relativePath = path.relative(process.cwd(), outputPath); responseContent.push({ type: "text", - text: `Screenshot saved to: ${filePath}`, - } as TextContent); - } - - // Handle base64 storage - if (args.storeBase64 !== false) { - screenshots.set(args.name, base64Screenshot); - server.notification({ - method: "notifications/resources/list_changed", + text: `Screenshot saved to: ${relativePath}`, }); - - responseContent.push({ - type: "image", - data: base64Screenshot, - mimeType: "image/png", - } as ImageContent); + + // Handle base64 storage, but only store it without returning image content + if (args.storeBase64 !== false) { + screenshots.set(args.name || 'screenshot', base64Screenshot); + server.notification({ + method: "notifications/resources/list_changed", + }); + + responseContent.push({ + type: "text", + text: `Screenshot also stored in memory with name: '${args.name || 'screenshot'}'`, + }); + } + + return { + content: responseContent, + isError: false, + }; + } catch (error) { + return { + content: [{ + type: "text", + text: `Screenshot failed: ${(error as Error).message}`, + }], + isError: true, + }; } - - return { - content: responseContent, - isError: false, - }; - } catch (error) { - return { - content: [{ - type: "text", - text: `Screenshot failed: ${(error as Error).message}`, - }], - isError: true, - }; } - } case "playwright_click": try { await page!.click(args.selector);