Skip to content

Release branch for v1.0.6 #148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 20, 2025
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ The evals package loads an mcp client that then runs the index.ts file, so there
OPENAI_API_KEY=your-key npx mcp-eval src/evals/evals.ts src/tools/codegen/index.ts
```

## Contributing

When adding new tools, please be mindful of the tool name length. Some clients, like Cursor, have a 60-character limit for the combined server and tool name (`server_name:tool_name`).

Our server name is `playwright-mcp`. Please ensure your tool names are short enough to not exceed this limit.

## Star History

[![Star History Chart](https://api.star-history.com/svg?repos=executeautomation/mcp-playwright&type=Date)](https://star-history.com/#executeautomation/mcp-playwright&Date)
17 changes: 17 additions & 0 deletions docs/docs/release.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ import YouTubeVideoEmbed from '@site/src/components/HomepageFeatures/YouTubeVide

# Release Notes

## Version 1.0.6
- **New Tool: `playwright_upload_file`**: Added a new tool to upload files to an `input[type='file']` element.
- **Enhanced Content Extraction**:
- Significantly improved `playwright_get_visible_text` tool for more accurate text extraction.
- Added `playwright_get_visible_html` tool to retrieve the full HTML content of the page.
- **By default, all `<script>` tags are now removed from the HTML output for `playwright_get_visible_html` (unless `removeScripts: false` is set). Output is also truncated to a safe length (default 20,000 characters) to prevent issues with LLM clients.**
- **Improved Interactions**:
- Updated `playwright_hover` functionality in the interaction tool.
- **Browser Support**:
- Added support for using locally installed browsers by specifying the Chrome executable path.
- **Documentation Updates**:
- Added a new local setup and installation guide.
- Updated examples and documentation for the new and improved tools.
- **Test Coverage**:
- Added and updated tests for the new content extraction and interaction features.
- **Version bump**: Incremented version to 1.0.6.

## Version 1.0.5
- **Removed SSE (Server-Sent Events) Support**: All SSE-related code, endpoints, and documentation have been fully removed. The server now only supports STDIO transport for communication with clients.
- **Codebase Cleanup**: Removed all references to SseServer, /events endpoint, and related event streaming features from the code and documentation.
Expand Down
67 changes: 30 additions & 37 deletions package-lock.json

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

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@executeautomation/playwright-mcp-server",
"version": "1.0.5",
"version": "1.0.6",
"description": "Model Context Protocol servers for Playwright",
"license": "MIT",
"author": "ExecuteAutomation, Ltd (https://executeautomation.com)",
Expand Down Expand Up @@ -28,12 +28,12 @@
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.11.1",
"@playwright/browser-chromium": "1.52.0",
"@playwright/browser-firefox": "1.52.0",
"@playwright/browser-webkit": "1.52.0",
"@playwright/test": "^1.52.0",
"@playwright/browser-chromium": "1.53.1",
"@playwright/browser-firefox": "1.53.1",
"@playwright/browser-webkit": "1.53.1",
"@playwright/test": "^1.53.1",
"mcp-evals": "^1.0.18",
"playwright": "1.52.0",
"playwright": "1.53.1",
"uuid": "11.1.0"
},
"keywords": [
Expand Down
24 changes: 24 additions & 0 deletions src/__tests__/toolHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,28 @@ describe('Tool Handler', () => {
]);
});
});

test('should use executablePath when CHROME_EXECUTABLE_PATH is set', async () => {
const mockExecutablePath = '/path/to/chrome';
process.env.CHROME_EXECUTABLE_PATH = mockExecutablePath;

// Call the navigation tool to trigger browser launch
await handleToolCall('playwright_navigate', { url: 'about:blank' }, mockServer);

// This is a proxy for checking if the executable path is being used.
// A more direct test would require exporting ensureBrowser and spying on it.
// For now, we will just check if the tool call succeeds.
const result = await handleToolCall('playwright_navigate', { url: 'about:blank' }, mockServer);
expect(result.content[0].text).toContain('Navigated to');

// Clean up
delete process.env.CHROME_EXECUTABLE_PATH;
});

test('should not launch browser for API tools', async () => {
const ensureBrowser = jest.spyOn(require('../toolHandler'), 'ensureBrowser');
await handleToolCall('playwright_get', { url: 'https://api.restful-api.dev/objects' }, mockServer);
expect(ensureBrowser).not.toHaveBeenCalled();
ensureBrowser.mockRestore();
});
});
2 changes: 1 addition & 1 deletion src/__tests__/tools/browser/visiblePage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ describe('VisibleHtmlTool', () => {
});

test('should retrieve HTML content', async () => {
const args = {};
const args = { removeScripts: false };

const result = await visibleHtmlTool.execute(args, mockContext);

Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { setupRequestHandlers } from "./requestHandler.js";
async function runServer() {
const server = new Server(
{
name: "executeautomation/playwright-mcp-server",
version: "1.0.5",
name: "playwright-mcp",
version: "1.0.6",
},
{
capabilities: {
Expand Down
12 changes: 8 additions & 4 deletions src/toolHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ async function registerConsoleMessage(page) {
/**
* Ensures a browser is launched and returns the page
*/
async function ensureBrowser(browserSettings?: BrowserSettings) {
export async function ensureBrowser(browserSettings?: BrowserSettings) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description: The function is long and handles multiple responsibilities, which could make it harder to maintain and understand. Consider breaking down the function into smaller, more focused functions for each main task (e.g., browser cleanup, browser launch, page creation).

Severity: Medium

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the comment by breaking down the ensureBrowser function into smaller, more focused functions. The main ensureBrowser function now calls three separate functions: cleanupDisconnectedBrowser, launchNewBrowserIfNeeded, and createNewPageIfNeeded (which needs to be implemented). This improves readability and maintainability by separating concerns and reducing the complexity of the main function.

Suggested change
export async function ensureBrowser(browserSettings?: BrowserSettings) {
* Ensures a browser is launched and returns the page
*/
export async function ensureBrowser(browserSettings?: BrowserSettings) {
await cleanupDisconnectedBrowser();
await launchNewBrowserIfNeeded(browserSettings);
return await createNewPageIfNeeded();
}
async function cleanupDisconnectedBrowser() {
if (browser && !browser.isConnected()) {
console.error("Browser exists but is disconnected. Cleaning up...");
try {
await browser.close().catch(err => console.error("Error closing disconnected browser:", err));
} catch (e) {
// Ignore errors when closing disconnected browser
}
resetBrowserState();
}
}
async function launchNewBrowserIfNeeded(browserSettings?: BrowserSettings) {
if (!browser) {
const { viewport, userAgent, headless = false, browserType = 'chromium' } = browserSettings ?? {};
// If browser type is changing, force a new browser instance
if (browser && currentBrowserType !== browserType) {
try {

try {
// Check if browser exists but is disconnected
if (browser && !browser.isConnected()) {
Expand Down Expand Up @@ -199,10 +199,14 @@ async function ensureBrowser(browserSettings?: BrowserSettings) {
browserInstance = chromium;
break;
}
// Read the Chrome executable path from the environment variable
const executablePath = process.env.PLAYWRIGHT_CHROME_EXECUTABLE_PATH || undefined; // Fallback to default if not set

browser = await browserInstance.launch({ headless, executablePath });
const executablePath = process.env.CHROME_EXECUTABLE_PATH;

browser = await browserInstance.launch({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description: The browser launch configuration doesn't include any performance-related options, which could impact the tool's efficiency. Consider adding performance-enhancing options to the browser launch configuration, such as args: ['--no-sandbox', '--disable-setuid-sandbox'] for Chromium-based browsers.

Severity: Medium

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the performance issue by adding performance-enhancing options to the browser launch configuration. Specifically, it adds the arguments '--no-sandbox' and '--disable-setuid-sandbox' to the launch options for Chromium-based browsers. These options can improve the browser's startup time and overall performance, especially in containerized environments.

Suggested change
browser = await browserInstance.launch({
browser = await browserInstance.launch({
headless,
executablePath: executablePath,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
currentBrowserType = browserType;

headless,
executablePath: executablePath
});

currentBrowserType = browserType;

// Add cleanup logic when browser is disconnected
Expand Down
7 changes: 4 additions & 3 deletions src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,17 +350,18 @@ export function createToolDefinitions() {
},
{
name: "playwright_get_visible_html",
description: "Get the HTML content of the current page",
description: "Get the HTML content of the current page. By default, all <script> tags are removed from the output unless removeScripts is explicitly set to false.",
inputSchema: {
type: "object",
properties: {
selector: { type: "string", description: "CSS selector to limit the HTML to a specific container" },
removeScripts: { type: "boolean", description: "Remove all script tags from the HTML (default: false)" },
removeScripts: { type: "boolean", description: "Remove all script tags from the HTML (default: true)" },
removeComments: { type: "boolean", description: "Remove all HTML comments (default: false)" },
removeStyles: { type: "boolean", description: "Remove all style tags from the HTML (default: false)" },
removeMeta: { type: "boolean", description: "Remove all meta tags from the HTML (default: false)" },
cleanHtml: { type: "boolean", description: "Perform comprehensive HTML cleaning (default: false)" },
minify: { type: "boolean", description: "Minify the HTML output (default: false)" }
minify: { type: "boolean", description: "Minify the HTML output (default: false)" },
maxLength: { type: "number", description: "Maximum number of characters to return (default: 20000)" }
},
required: [],
},
Expand Down
22 changes: 19 additions & 3 deletions src/tools/browser/visiblePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,15 @@ export class VisibleTextTool extends BrowserToolBase {
}
return text.trim();
});
return createSuccessResponse(`Visible text content:\n${visibleText}`);
// Truncate logic
const maxLength = typeof args.maxLength === 'number' ? args.maxLength : 20000;
let output = visibleText;
let truncated = false;
if (output.length > maxLength) {
output = output.slice(0, maxLength) + '\n[Output truncated due to size limits]';
truncated = true;
}
return createSuccessResponse(`Visible text content:\n${output}`);
} catch (error) {
return createErrorResponse(`Failed to get visible text content: ${(error as Error).message}`);
}
Expand Down Expand Up @@ -83,7 +91,9 @@ export class VisibleHtmlTool extends BrowserToolBase {
}
return this.safeExecute(context, async (page) => {
try {
const { selector, removeScripts, removeComments, removeStyles, removeMeta, minify, cleanHtml } = args;
const { selector, removeComments, removeStyles, removeMeta, minify, cleanHtml } = args;
// Default removeScripts to true unless explicitly set to false
const removeScripts = args.removeScripts === false ? false : true;

// Get the HTML content
let htmlContent: string;
Expand Down Expand Up @@ -170,7 +180,13 @@ export class VisibleHtmlTool extends BrowserToolBase {
);
}

return createSuccessResponse(`HTML content:\n${htmlContent}`);
// Truncate logic
const maxLength = typeof args.maxLength === 'number' ? args.maxLength : 20000;
let output = htmlContent;
if (output.length > maxLength) {
output = output.slice(0, maxLength) + '\n<!-- Output truncated due to size limits -->';
}
return createSuccessResponse(`HTML content:\n${output}`);
} catch (error) {
return createErrorResponse(`Failed to get visible HTML content: ${(error as Error).message}`);
}
Expand Down
Loading