TL;DR
Playwright MCP v0.0.71 ships browser_drop. It gives you native drag-and-drop from any MCP client. No more evaluate scripts. No more mouse.move chains. Grid reordering, file drop zones, text editor drags — all work the same way a real user does.
Why This Release Matters
QA teams either abandon drag-and-drop testing or hack around it. But sortable grids, file uploads, and rich text editors are everywhere. And they have been painful to test forever.
I ran into this firsthand on one project. Solid Playwright coverage for clicks, typing, and navigation. But drag-and-drop? We used evaluate scripts. Or we tested it by hand. Both paths broke across browsers. Both were impossible to keep working.
Playwright MCP v0.0.71 fixes this with browser_drop. It uses Playwright's own Locator.drop — the same API your tests already use. Now any MCP client can call it.
How to Use browser_drop
Here's a complete example combining browser_drop with the new response body capture from browser_network_requests and the simplified expression support in browser_evaluate. This pipeline automates a file upload scenario, validates the server response, and confirms the UI state update:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({
name: "file-upload-automation",
version: "1.0.0",
});
// Drop zone and file item selectors for a document management UI
const dropZoneSelector = '[data-testid="upload-zone"]';
const fileItemSelector = '[data-testid="file-item"]';
const uploadedStatusSelector = '[data-testid="upload-status"]';
// Tool: Simulate file drag-and-drop onto upload zone
server.addTool("upload_document_flow", {
description: "Upload a document via drag-and-drop and validate response",
inputSchema: {
type: "object",
properties: {
fileName: { type: "string", description: "Name of file to upload" },
fileId: { type: "string", description: "Unique file identifier" },
},
},
async execute(fileName, fileId) {
// Navigate to upload interface
await server.tools.call("browser_navigate", { url: "https://internal-docs.example.com/upload" });
// Locate drag source and drop target
const dragSource = `text=${fileName}`;
const dropTarget = dropZoneSelector;
// Execute native drag-and-drop operation
// browser_drop wraps Locator.drop - no evaluate or mouse.move workarounds
const dropResult = await server.tools.call("browser_drop", {
source: dragSource,
target: dropTarget,
});
if (!dropResult.success) {
return { error: `Drop operation failed: ${dropResult.error}` };
}
// Inspect server response body with mime-type detection
const networkCapture = await server.tools.call("browser_network_requests", {
urlPattern: "**/api/upload**",
responseBody: true,
responseHeaders: true,
});
// Extract upload confirmation
const uploadResponse = networkCapture.requests?.[0];
if (!uploadResponse?.responseBody) {
return { error: "No upload response captured" };
}
// Validate response using plain expression (no function wrapper needed)
const validationResult = await server.tools.call("browser_evaluate", {
expression: `JSON.parse(arguments[0]).status === "success"`,
args: [uploadResponse.responseBody],
});
// Confirm UI reflects successful upload
const statusText = await server.tools.call("browser_evaluate", {
expression: `document.querySelector("${uploadedStatusSelector}")?.textContent`,
});
return {
uploaded: true,
serverResponse: uploadResponse.responseBody,
uiStatus: statusText.result,
validationPassed: validationResult.result === true,
};
},
});
Three new tools working together: browser_drop handles the drag. browser_network_requests captures the server response (full body, not just status codes). browser_evaluate runs plain JavaScript — no function wrapper needed.
The Gotcha Nobody Is Talking About
browser_drop needs both elements to be on screen. That's correct Playwright behavior. But here's the catch: if you navigate to a page and the drag target sits below the fold, the drop fails.
The fix: Call browser_evaluate to scroll the target into view before calling browser_drop, or use the scroll option if your Playwright version supports it. This catches teams off guard in CI where viewport sizes are smaller than local development.
// Before browser_drop: ensure target is in viewport
await server.tools.call("browser_evaluate", {
expression: `document.querySelector("${dropTarget}").scrollIntoView()`,
});
This is not a bug. It's how Playwright works. But it catches teams when they test on a big screen and deploy to CI. CI viewports are smaller. The element you tested locally is off screen in the pipeline.
What This Changes in Your CI Pipeline
With browser_drop, you can test drag-and-drop flows through MCP. Not by hand. Not with broken scripts.
On one project, Selenium to Playwright gave us 40% faster tests. But drag-and-drop still broke in headless mode. We wrote evaluate scripts. They stopped working every sprint. browser_drop puts native drag-and-drop into MCP. No scripts. No workarounds.
What this actually means:
-
Fewer flaky tests. Native drag-and-drop is tested across browsers.
evaluate+mouse.movesequences are not. -
Simpler AI test generation. AI tools call
browser_dropdirectly. No fragile mouse chains. - Faster CI. Native operations run faster than JavaScript-injected drag scripts.
Verdict
Playwright MCP v0.0.71 is worth upgrading for browser_drop alone. The response body capture and plain expression support make it better. But drag-and-drop was the missing piece. Now it's there.
The catch is real but small. Scroll your target into view before you drop. One line. Add it to your tool definitions and move on.
If you run MCP-based test infrastructure, this kills the last reason to fall back to evaluate for drag-and-drop. Upgrade. Add the scroll guard. Ship.
Reference: Playwright Locator.drop API documentation
Anton Gulin is an AI QA Architect — the first person to claim this title on LinkedIn. He builds AI-powered test automation systems where AI agents and human engineers collaborate on quality. Former Apple SDET, now Lead Software Engineer in Test. Find him at anton.qa or on LinkedIn.
Top comments (0)