Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/docs/release.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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)",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
137 changes: 70 additions & 67 deletions src/toolsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading