Drag’n’drop is one of those UI interactions that used to feel painful to automate.
Rarely used, often avoided… but very real in modern products, especially a Agentic Workflow Builders like Opus or n8n, OpenAI, Monday.com, etc.
Workflow builders, canvas editors, node-based UIs — like Opus — rely on it heavily.
The good news: Cypress (with the right approach) handles this surprisingly well.
Two main ways to do drag’n’drop in Cypress
Native browser events
.trigger("mousedown")
.trigger("mousemove")
.trigger("mouseup")
This gives you full control over coordinates and is perfect for:
- canvas-based UIs
- node connectors
- custom drag logic
Cypress-real-events plugin
Closer to real user behavior and great for classic UI drag cases.
Real-world example: connecting nodes on a canvas.
This is how I automate workflow connections in Opus
// Cypress Custom Command to connect workflow units - Tasks(Nodes).
Cypress.Commands.add("connectNodes", (outputSelector, inputSelector, numberOfEdges) => {
function getCenterCoords($el) {
const rect = $el[0].getBoundingClientRect();
return {
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2,
};
}
cy.get(outputSelector).then(($out) => {
const { x: outX, y: outY } = getCenterCoords($out);
cy.get(inputSelector).then(($in) => {
const { x: inX, y: inY } = getCenterCoords($in);
cy.wrap($out)
.trigger("mousedown", { button: 0, clientX: outX, clientY: outY, force: true })
.trigger("mousemove", { clientX: inX, clientY: inY, force: true });
cy.wrap($in).trigger("mouseup", { force: true });
cy.get("[data-test-id='edge']")
.should("exist")
.and("have.length", numberOfEdges);
});
});
});
This approach lets tests build workflows automatically, just like real users do.
And bonus: file uploads.
While it seems to be the same method, it really depends on which packages your product is using. So there is 2 approaches:
- .selectFile() - built-in way to select files in an HTML5 input element & simulate dragging files into a browser with the .selectFile()
- Native browser events:
.trigger("dragenter")
.trigger("dragover")
.trigger("drop")
// Cypress Custom Command to drag'n'drop file into upload area, e.g. React Aria DropZone
Cypress.Commands.add(
"dragAndDropUploadFile",
(fileName, targetSelector, mimeType = "application/json") => {
cy.intercept("POST", "/job/file/download").as("fileDownload");
cy.fixture(`sampleFiles/${fileName}`, "base64").then((fileContent) => {
const blob = Cypress.Blob.base64StringToBlob(fileContent, mimeType);
const testFile = new File([blob], fileName, { type: mimeType });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(testFile);
cy.get(targetSelector)
.trigger("dragenter", { dataTransfer })
.trigger("dragover", { dataTransfer })
.trigger("drop", { dataTransfer });
});
// Verify status of the upload process with custom command
cy.verifyApiStatus("@fileDownload", 201);
},
);
Key takeaway
Drag’n’drop isn’t just about moving pixels.
It’s about testing user intent, state transitions, and real AI workflows.
Once you stop avoiding it — Cypress gives you everything you need.
Next up: more canvas, workflow, and agentic UI testing patterns 👀
How do you handle drag’n’drop in your tests?
Top comments (0)