Search code examples
javascriptreactjsdrag-and-dropcypress

Cypress - Drag and drop not working on a react-based website


I'm struggling with Cypress for any drag and drop actions on a react-based website. The actions don't fail in the Cypress dashboard, but the items simply don't get dragged.

What I have is a set of divs which are a list of pages that are "In Menu" (so they are visible to users), and another set of divs which are "Not In Menu" (so they are not visible to the users). What I want to do is move the "Not In Menu" pages into the "In Menu" section.

These are the elements in the website:

The "draggable" item:

<div data-testid='pages-section-not-in-menu-list' data-rbd-droppable-id="notInMenu" data-rbd-droppable-context-id="0" class="draggable-place">
    <div class="page1">Page1</div>
    <div class="page2">Page2</div>
    <div class="page3">Page3</div>
</div>

The "droppable" area:

<div data-testid='pages-section-in-menu-list' data-rbd-droppable-id="inMenu" data-rbd-droppable-context-id="0" class="droppable-place">
</div>

My code:

public dragAndDropPagesToInMenu(): void {

        const dataTransfer = new DataTransfer();
        cy.wait(3000);
        cy.log("Dragging one page to `In Menu` section");
        cy.get("div[class='page3']")
            .first()
            .trigger('dragstart', { dataTransfer });
        cy.get("div[data-testid='pages-section-in-menu-list']")
            .eq(0)
            .trigger('drop', { dataTransfer });
        cy.get("div[class='page3']")
            .last()
            .trigger('dragend');

    }

I've also tried the following solutions, but none of them have worked so far:

Any ideas?


Solution

  • To simplify @Agustin Catalano's answer, you can make the custom command a child command, by passing { prevSubject: "element" } as the second argument to Cypress.Commands.add, like so:

    // cypress/support/commands.ts
    
    declare global {
        namespace Cypress {
            interface Chainable {
                /** Custom command to drag subject to target */
                drag(target: string, options?: Partial<TypeOptions>): Chainable<Element>
    
            }
        }
    }
    
    
    /** Adds custom command `cy.drag` to the global `cy` object  */
    Cypress.Commands.add("drag", { prevSubject: "element" }, (
      subject: Cypress.JQueryWithSelector<HTMLElement>, 
      target: string, 
      _options?: Partial<Cypress.TypeOptions>
    ) => {
        const BUTTON_INDEX = 0;
        const SLOPPY_CLICK_THRESHOLD = 10;
    
        cy.get(target)
            .first()
            .then($target => {
                let coordsDrop = $target[0].getBoundingClientRect();
    
                const coordsDrag = subject[0].getBoundingClientRect();
                cy.wrap(subject)
                    .trigger("mousedown", {
                        button: BUTTON_INDEX,
                        clientX: coordsDrag.x,
                        clientY: coordsDrag.y,
                        force: true
                    })
                    .trigger("mousemove", {
                        button: BUTTON_INDEX,
                        clientX: coordsDrag.x + SLOPPY_CLICK_THRESHOLD,
                        clientY: coordsDrag.y,
                        force: true
                    });
                cy.get("body")
                    .trigger("mousemove", {
                        button: BUTTON_INDEX,
                        clientX: coordsDrop.x,
                        clientY: coordsDrop.y,
                        force: true
                    })
                    .trigger("mouseup");
            });
    });
    

    This way, you only have to specify the selector of the subject once in the query chain:

    cy.get("subject").drag("target")