53

I am struggling to test drag and drop with Cypress and Angular Material Drag and Drop. So the goal is to move "Get to work" from Todo to Done. I have created the following test, that should make it easy for you to reproduce:

You can play with the Stackblitz here.

describe('Trying to implement drag-n-drop', () => {

    before(() => {
        Cypress.config('baseUrl', null);

        cy.viewport(1000, 600);
        cy.visit('https://angular-oxkc7l-zirwfs.stackblitz.io')
        .url().should('contain', 'angular')
        .get('h2').should('contain', 'To do');
    });

    it('Should work, based on this https://stackoverflow.com/a/54119137/3694288', () => {

        const dataTransfer = new DataTransfer;

        cy.get('#cdk-drop-list-0 > :nth-child(1)')
            .trigger('dragstart', { dataTransfer });

        cy.get('#cdk-drop-list-1')
            .trigger('drop', { dataTransfer });

        cy.get('#cdk-drop-list-0 > :nth-child(1)')
            .trigger('dragend');

        cy.get('#cdk-drop-list-1').should('contain', 'Get to work');
    });


    it('Should work, with this library https://github.com/4teamwork/cypress-drag-drop', () => {
        cy.get('#cdk-drop-list-0 > :nth-child(1)')
            .drag('#cdk-drop-list-1');

        cy.get('#cdk-drop-list-1').should('contain', 'Get to work');
    });

});

The result from running the above test, looks like this:

enter image description here

Here is a repo to develop a solution.

Thanks for the help.

Events fired, found using the chrome debugger:

Item

  • pointerover
  • pointerenter
  • mouseover
  • mousedown
  • pointermove
  • mousemove
  • pointerout
  • pointerleave
  • mouseout
  • mouseleave

Drop zone

  • pointerover
  • pointerenter
  • mouseover
  • pointermove
  • mousemove
  • pointerleave
  • mouseout
  • mouseleave

Solution

After @Richard Matsen's awesome answer I ended up with adding his answer as a custom command. The solution looks like this

support/drag-support.ts

    export function drag(dragSelector: string, dropSelector: string) {
        // Based on this answer: https://stackoverflow.com/a/55436989/3694288
        cy.get(dragSelector).should('exist')
          .get(dropSelector).should('exist');
    
          const draggable = Cypress.$(dragSelector)[0]; // Pick up this
          const droppable = Cypress.$(dropSelector)[0]; // Drop over this
    
          const coords = droppable.getBoundingClientRect();
          draggable.dispatchEvent(<any>new MouseEvent('mousedown'));
          draggable.dispatchEvent(<any>new MouseEvent('mousemove', {clientX: 10, clientY: 0}));
          draggable.dispatchEvent(<any>new MouseEvent('mousemove', {
              // I had to add (as any here --> maybe this can help solve the issue??)
              clientX: coords.left + 10,
              clientY: coords.top + 10  // A few extra pixels to get the ordering right
          }));
          draggable.dispatchEvent(new MouseEvent('mouseup'));
          return cy.get(dropSelector);
    }

support/commands.ts

    // Add typings for the custom command
    declare global {
        namespace Cypress {
            interface Chainable {
                drag: (dragSelector: string, dropSelector: string) => Chainable;
            }
        }
    }
    // Finally add the custom command
    Cypress.Commands.add('drag', drag);

in the spec file

    it(' Thx to Stackoverflow, drag and drop support now works ', () => {
       cy.drag('#cdk-drop-list-0 > :nth-child(1)', '#cdk-drop-list-1')
       .should('contain', 'Get to work');
    });

A small giph, because I'm just so happy it finally works

enter image description here

CI

Now it also works in CI (and electron locally). Tested with CircleCI 2.0.

Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
DauleDK
  • 3,313
  • 11
  • 55
  • 98
  • I haven't tested it, but (potentially) `cy.get(dragSelector).should('exist')` in `function drag` won't guard against async loading, Commands just tell Cypress "put this test in the queue and run it asap", then the code carries on with the next lines (which are sync so execute immediately). You could prevent that with nested `then()`s, or perhaps a `cy.wrap().then()` around the sync code which turns it into a queued block. – Richard Matsen Mar 31 '19 at 19:01
  • Thanks for the comment @RichardMatsen. Because Cypress "just works" most of the time, I normally just have `.get()`in many of my tests. Thanks for your input, I will test the async nature of the drag method. I have also been thinking that the `drag` method should actually only have the target as input, the source should be provided in the `chain` :) – DauleDK Mar 31 '19 at 19:23
  • has this been tested with elements in an iframe? – methuselah Apr 28 '20 at 17:52
  • no - this was not done in an iframe. – DauleDK Apr 29 '20 at 11:21
  • This doesn't seem to work anymore. Are you familiar with any necessary changes for latest Angular and Cdk drag and drop? – Wilt Jan 28 '22 at 20:58
  • @Wilt we moved from Angular to Svelte a long time ago - but some of the newer answers might work? Let me know which is the best solution so I can update the recommended answer. – DauleDK Jan 29 '22 at 18:37
  • For those struggling with Cdk drag and drop functionality in their tests; this answer here: https://stackoverflow.com/a/71968175/1697459 worked for me in Angular 13 with Cypress 10. – Wilt Oct 25 '22 at 09:14

18 Answers18

28

Dispatching MouseEvents seems to be the only way to test Angular Material drag and drop.

You should also be aware of the following issue, which tests in Protractor but also applies to this Cypress test

CDK DragDrop Regression between 7.0.0-beta.2 and 7.0.0-rc.2: Protractor tests stopped working #13642,

It seems that (for want of a better explanation) an additional nudge is needed on the mousemove.

The steps given as a workaround (Protractor syntax),

private async dragAndDrop ( $element, $destination ) {
  await browser.actions().mouseMove( $element ).perform();
  await browser.actions().mouseDown( $element ).perform();
  await browser.actions().mouseMove( {x: 10, y: 0 } ).perform();
  await browser.actions().mouseMove( $destination ).perform();
  return browser.actions().mouseUp().perform();
}

can be translated into a Cypress test, the simplest form I found is

it('works (simply)', () => {
  const draggable = Cypress.$('#cdk-drop-list-0 > :nth-child(1)')[0]  // Pick up this
  const droppable = Cypress.$('#cdk-drop-list-1 > :nth-child(4)')[0]  // Drop over this

  const coords = droppable.getBoundingClientRect()
  draggable.dispatchEvent(new MouseEvent('mousedown'));
  draggable.dispatchEvent(new MouseEvent('mousemove', {clientX: 10, clientY: 0}));
  draggable.dispatchEvent(new MouseEvent('mousemove', {
    clientX: coords.x+10,   
    clientY: coords.y+10  // A few extra pixels to get the ordering right
  }));
  draggable.dispatchEvent(new MouseEvent('mouseup'));

  cy.get('#cdk-drop-list-1').should('contain', 'Get to work');
  cy.get('#cdk-drop-list-1 > .cdk-drag').eq(3).should('contain', 'Get to work');

});

Notes

  • The problem in the referenced issue is not limited to Protractor. If you remove the first mousemove in the Cypress test, it also fails.
  • The cy.get(..).trigger() syntax does not seem to work with Angular, but native dispatchEvent() does.
  • Dragging over a specific element in the target list (as opposed to just dropping on the list) gives precise positioning within the target list.
  • dragstart, dragend may not be appropriate for Angular Material, as the code shows the event received is type CdkDragDrop rather than a DataTransfer object.
  • If content is asynchronously fetched, you may have to switch from Cypress.$(...) to cy.get(...).then(el => {...}), to take advantage of cypress' auto retry in commands.
  • I had to add a 10 second timeout to visit the Stackblitz url.

Async list fetching

If the list is fetched by an async Angular service (httpClient) during component construction, using this in the test

const draggable = Cypress.$('#cdk-drop-list-0 > :nth-child(1)')[0]

will not work, because the nth-child will not be present immediately, only after the fetch completes.

Instead, you can use cy.get() to provide retries up to a timeout (default 5 seconds).

cy.get('#cdk-drop-list-0 > :nth-child(1)').then(el => {
  const draggable = el[0]  // Pick up this
  cy.get('#cdk-drop-list-1 > :nth-child(4)').then(el => {
    const droppable = el[0]  // Drop over this

    const coords = droppable.getBoundingClientRect()
    draggable.dispatchEvent(new MouseEvent('mousemove'));
    draggable.dispatchEvent(new MouseEvent('mousedown'));
    draggable.dispatchEvent(new MouseEvent('mousemove', {clientX: 10, clientY: 0}));
    draggable.dispatchEvent(new MouseEvent('mousemove', {clientX: coords.x+10, clientY: coords.y+10}));
    draggable.dispatchEvent(new MouseEvent('mouseup'));

  })

  cy.get('#cdk-drop-list-1').should('contain', 'Get to work');
  cy.get('#cdk-drop-list-1 > .cdk-drag').eq(3).should('contain', 'Get to work');
})

or my preference is to use a 'canary' test to ensure loading is complete, something like

before(() => {
  cy.get('#cdk-drop-list-0 > :nth-child(1)') // Canary - wait 5s for data
})

it('should...', () => {
  const draggable = Cypress.$('#cdk-drop-list-0 > :nth-child(1)')[0]  // Pick up this
  const droppable = Cypress.$('#cdk-drop-list-1 > :nth-child(4)')[0]  // Drop over this
  ...
})

Typescript support

Warning - this is a quick hack to get over Typescript compiler problems, and could be improved.

const coords: ClientRect = droppable.getBoundingClientRect()
draggable.dispatchEvent(new (<any>MouseEvent)('mousemove'));
draggable.dispatchEvent(new (<any>MouseEvent)('mousedown'));
draggable.dispatchEvent(new (<any>MouseEvent)('mousemove', {clientX: 10.0, clientY: 0.0}));
draggable.dispatchEvent(new (<any>MouseEvent)('mousemove', {clientX: coords.left + 10.0, clientY: coords.top + 10.0}));
draggable.dispatchEvent(new (<any>MouseEvent)('mouseup'));
Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
  • Hi Richard - the solution works great. What's the difference between `Cypress.$(...)` and `cy.get(...)`? I have not seen `Cypress` used before that way? – DauleDK Mar 31 '19 at 08:19
  • 2
    `Cypress.$(...)` is jquery exposed on the Cypress global object. It returns a result but does not fail the test if the result is undefined (i.e the selector is not present). That could happen if you fetch the list items via http (in your example, the list is hard-coded so it works ok). On the other hand, `cy.get(...)` essentially repeats `Cypress.$(...)` until it succeeds, or until timeout at which time the test fails. The cost of using `cy.get(...)` is that you need to nest downstream code inside `.then(...)`. – Richard Matsen Mar 31 '19 at 09:06
  • I will add a couple of examples for async list fetching. – Richard Matsen Mar 31 '19 at 09:07
  • Thanks for the awesome explanation, makes sense. I just realized that the solution does not work in CI unfortunately. I tried locally with Electron, and here it fails as well. Do you have any idea what could be the reason for the error `TypeError: Failed to construct 'MouseEvent': The provided double value is non-finite.`? – DauleDK Mar 31 '19 at 09:14
  • 1
    That's an interesting error message. I'll take a look (might have to be tomorrow). If you could update your repo, that will give me a head start. – Richard Matsen Mar 31 '19 at 09:25
  • The repo has now been updated, both to support typescript and cleaned up a bit. – DauleDK Mar 31 '19 at 10:06
  • This does not work for me but @bkucera above does the trick in my case. – jmcollin92 Dec 28 '19 at 00:10
15

I've written up a small example for how to implement drag and drop.

It works by adding a dragTo command like so:

/// <reference types="cypress"/>

it('works', () => {
  cy.visit('https://angular-oxkc7l-zirwfs.stackblitz.io/')
  cy.contains('To do', { timeout: 15000 }) // ensure page is loaded -__-

  const item = '.example-box:not(.cdk-drag-placeholder)'

  cy.get('#cdk-drop-list-1').children(item).should('have.length', 5)

  cy.get('.example-box:contains("Get to work")').dragTo('.example-box:contains("Get up")')
  cy.get('#cdk-drop-list-1').children(item).should('have.length', 6)

  // interpolates 10 extra mousemove events on the way
  cy.get('#cdk-drop-list-0').dragTo('#cdk-drop-list-1', { steps: 10 })
  cy.get('#cdk-drop-list-1').children(item).should('have.length', 7)

  // sets steps >= 10
  cy.get('#cdk-drop-list-0').dragTo('#cdk-drop-list-1', { smooth: true })
  cy.get('#cdk-drop-list-1').children(item).should('have.length', 8)

  cy.get('#cdk-drop-list-0').dragTo('#cdk-drop-list-1')
  cy.get('#cdk-drop-list-1').children(item).should('have.length', 9)
})

To add it, try putting this in your support/index.js or pasting it at the bottom of a spec file (warning: poor code quality):


const getCoords = ($el) => {
  const domRect = $el[0].getBoundingClientRect()
  const coords = { x: domRect.left + (domRect.width / 2 || 0), y: domRect.top + (domRect.height / 2 || 0) }

  return coords
}

const dragTo = (subject, to, opts) => {

  opts = Cypress._.defaults(opts, {
    // delay inbetween steps
    delay: 0,
    // interpolation between coords
    steps: 0,
    // >=10 steps
    smooth: false,
  })

  if (opts.smooth) {
    opts.steps = Math.max(opts.steps, 10)
  }

  const win = subject[0].ownerDocument.defaultView

  const elFromCoords = (coords) => win.document.elementFromPoint(coords.x, coords.y)
  const winMouseEvent = win.MouseEvent

  const send = (type, coords, el) => {

    el = el || elFromCoords(coords)

    el.dispatchEvent(
      new winMouseEvent(type, Object.assign({}, { clientX: coords.x, clientY: coords.y }, { bubbles: true, cancelable: true }))
    )
  }

  const toSel = to

  function drag (from, to, steps = 1) {

    const fromEl = elFromCoords(from)

    const _log = Cypress.log({
      $el: fromEl,
      name: 'drag to',
      message: toSel,
    })

    _log.snapshot('before', { next: 'after', at: 0 })

    _log.set({ coords: to })

    send('mouseover', from, fromEl)
    send('mousedown', from, fromEl)

    cy.then(() => {
      return Cypress.Promise.try(() => {

        if (steps > 0) {

          const dx = (to.x - from.x) / steps
          const dy = (to.y - from.y) / steps

          return Cypress.Promise.map(Array(steps).fill(), (v, i) => {
            i = steps - 1 - i

            let _to = {
              x: from.x + dx * (i),
              y: from.y + dy * (i),
            }

            send('mousemove', _to, fromEl)

            return Cypress.Promise.delay(opts.delay)

          }, { concurrency: 1 })
        }
      })
      .then(() => {

        send('mousemove', to, fromEl)
        send('mouseover', to)
        send('mousemove', to)
        send('mouseup', to)
        _log.snapshot('after', { at: 1 }).end()

      })

    })

  }

  const $el = subject
  const fromCoords = getCoords($el)
  const toCoords = getCoords(cy.$$(to))

  drag(fromCoords, toCoords, opts.steps)
}

Cypress.Commands.addAll(
  { prevSubject: 'element' },
  {
    dragTo,
  }
)

enter image description here

kuceb
  • 16,573
  • 7
  • 42
  • 56
  • 1
    Hi @bkucera looks awesome. I'm trying to integrate this into my project, but we are using typescript. I get multiple errors with the `dragTo` method. Would you like to help resolve the issues? The first one is this line: `return Cypress.Promise.try(() => {` returns `Argument of type '() => Bluebird | undefined' is not assignable to parameter of type '() => void[] | PromiseLike'.` – DauleDK Mar 29 '19 at 06:14
  • 1
    That might have to be left as an exercise for the reader. Or simply keep the file in js, and add type definitions to Cypress for the custom command – kuceb Mar 30 '19 at 17:47
  • Hi @bkucera your method is the only one which works fpr me !. To make it work I have to set some delay, steps and smooth on: { delay: 50, steps: 30, smooth: true }. Even method from richard matsem above don't work for me. Many many thanks for your help – jmcollin92 Dec 28 '19 at 00:08
  • Hi @bkucera implemented your code and it is working. I am facing one more issue is like to forcibly drag the element and drop-in. Is that solution already implemented in your code?? – Ravikiran K Feb 20 '20 at 06:41
  • Any guidance how to use this with typescript? – Ali Turab Abbasi Jul 02 '20 at 12:19
  • the only answer here that actually worked for me – Martin Cremer Jun 15 '21 at 17:04
  • Very nice, works great with cdk drag and drop. – Wilt Nov 23 '21 at 14:42
  • Thanks for a great answer. Why don't you create a cypress drag and drop plugin? I tried https://github.com/4teamwork/cypress-drag-drop but it's not working well. – Abdug'affor Abdurahimov Dec 15 '21 at 05:43
6

After a lot of battling, I managed to make the drag and drop work with this:

cy.get('.list .item')
      .contains(startpos)
      .trigger('dragstart', { dataTransfer: new DataTransfer });
cy.get('.list .item')
      .eq(endpos)
      .trigger('drop')
      .trigger('dragend');

Pretty easy to use.

Baronvonbirra
  • 669
  • 1
  • 9
  • 14
2

Did you take a look at the official recipe that does exactly the same?

It uses this combination of triggered events

cy.get('.selector')
  .trigger('mousedown', { which: 1 })
  .trigger('mousemove', { clientX: 400, clientY: 500 })
  .trigger('mouseup', {force: true})

to drag&drop the item, let me know if you need some more help when you have tried it

NoriSte
  • 3,589
  • 1
  • 19
  • 18
  • I asked Bahmutov for help on twitter, and he adviced me to implement the code snippet you have there. But I am stuck with the mousemove. Would LOVE your help :) https://twitter.com/bahmutov/status/1110589982241161217 – DauleDK Mar 26 '19 at 17:54
  • Could you share a repository please? So we can easily share the various attempts – NoriSte Mar 26 '19 at 18:44
  • I'm trying it but I think that the problem is identifying carefully which element is listened to dispatch which event – NoriSte Mar 27 '19 at 06:53
  • Thanks for helping out. I used the chrome dev tools `getEventListeners` to inspect the drop zone, and here an event is listed as 'cdkDropListDropped'. So I'm wondering if I'm supposed to trigger such an event. Don't have much experience with custom event, but reading up on it. And thanks for the help buddy! – DauleDK Mar 27 '19 at 07:03
  • I don't think you need to trigger a custom event, custom events are usually used internally by the libraries. The hard part is to understand who is listened for which event, I mean: probably the `mousedown` event has listened on the item itself. The `mousemove` event hasn't listened on it, nor the container... but probably the whole drop zone (if not the page) because you can drag the item outside from its container... and the `mousedown` event... I don't know exactly, it needs to be discovered too. Take a look at the source code, it could be helpful – NoriSte Mar 27 '19 at 07:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/190751/discussion-between-dauledk-and-noriste). – DauleDK Mar 27 '19 at 07:46
  • @NoriSte : can you please check , i had already opened another question without going through this thread. https://stackoverflow.com/questions/63010048/why-is-drag-drop-not-working-as-per-expectation-in-cypress-io?noredirect=1#comment111426457_63010048 – Karan Jul 21 '20 at 17:20
1

Here's my cypress command for this:

Cypress.Commands.add(
  'dragTo',
  (selector: string, position: { x: number; y: number }) => {
    const log = Cypress.log({
      message: `Drag ${selector} to (${position.x}, ${position.y})`,
      consoleProps: () => ({ selector, position })
    });
    log.snapshot('before');
    const ret = cy
      .get(selector, { log: false })
      .trigger('mouseover', { force: true, log: false })
      .trigger('mousedown', {
        button: 0,
        log: false
      })
      .trigger('mousemove', {
        pageX: 10,
        pageY: 10,
        log: false
      })
      .then(el => {
        log.snapshot('Drag start');
        return el;
      })
      .trigger('mousemove', {
        pageX: position.x,
        pageY: position.y,
        force: true,
        log: false
      })
      .then(event => {
        log.snapshot('Drag End');
        return event;
      })
      .trigger('mouseup', { force: true, log: false })
      .then(() => {
        log.snapshot('after');
      });
    log.end();
    return ret;
  }
);
tedian_dev
  • 54
  • 4
1

Try this:

  it('should drag and drop the element', () => {
    const myItem = cy.get('my-item').first();
    myItem.trigger('mousedown', 100, 100, { force: true }).trigger('mousemove', 300, 300, { force: true });
    myItem.click().trigger('mouseup', { force: true });
});
1

In case anyone is also struggling with cdkDropListEntered not being triggered, you might want to check if there is any scrolling going on.

Since scrolling is handled by CDK (e.g. https://github.com/angular/components/blob/master/src/cdk/scrolling/viewport-ruler.ts#L131), I had to add the scroll position to any mouse events. The scroll position is computed like this (corresponding to above link):

const win = subject[0].ownerDocument.defaultView;
const window = win;
const document = window.document;
const documentElement = document.documentElement;
const documentRect = documentElement.getBoundingClientRect();

const top =
  -documentRect.top ||
  document.body.scrollTop ||
  window.scrollY ||
  documentElement.scrollTop ||
  0;

const left =
  -documentRect.left ||
  document.body.scrollLeft ||
  window.scrollX ||
  documentElement.scrollLeft ||
  0;

where subject is the result yielded e.g. by a cy.get command.

1

If there is not any steps involved in beween drag and drop then use below steps :

Download this for running scripts having drag and drop

npm install --save-dev @4tw/cypress-drag-drop

Go to cypress.json

"compilerOptions": {
            "types": ["cypress", "@4tw/cypress-drag-drop"]
            }

add this on cypress.json

Go on command.js

 require('@4tw/cypress-drag-drop')

USE

cy.get('Class or id which you want to drag ').drag('Class or id where you want to drop')
0

Not Angular specific, but should be generic and simple enough to tweak if needed. I did try a lot of recipes out there and also cypress-file-upload but that wouldn't work with webp for example.

The command below seems to work for most cases and reflects pretty closely what a user would do

Cypress.Commands.add('dropFile', {prevSubject: true}, (subject, fileName, fileType) => {
  return cy.fixture(fileName, 'binary').then((data) => {
    return Cypress.Blob.binaryStringToBlob(data, fileType).then(blob => {
      const file = new File([blob], fileName, {type: fileType});
      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(file);
      cy.wrap(subject)
        .trigger("dragenter", {force: true})
        .trigger("drop", {dataTransfer})
    })
  })
})

Ensure fixturesFolder is specified in your cypress.json config file. Then you simply use like below

cy.get("#dropzone").dropFile("myfile1.webp", "image/webp")
cy.get("#dropzone").dropFile("myfile2.jpg", "image/jpeg")
Pithikos
  • 18,827
  • 15
  • 113
  • 136
0

I kept having the problem with dropevent.isPointerOverContainer always being false with the other solutions here, so instead of mouseup at the end, I had to use click(). It was the only way to get the pointer position and drag position to be in the correct location to fire the drop() event in my component.

export function drag(dragSelector: string, dropSelector: string) {
  // Based on this answer: https://stackoverflow.com/questions/55361499/how-to-implement-drag-and-drop-in-cypress-test
  cy.get(dragSelector).should('exist').get(dropSelector).should('exist');

  const draggable = Cypress.$(dragSelector)[0]; // Pick up this
  const droppable = Cypress.$(dropSelector)[0]; // Drop over this
  const coords = droppable.getBoundingClientRect();

  draggable.dispatchEvent(<any>new MouseEvent('mousedown'));
  draggable.dispatchEvent(<any>new MouseEvent('mousemove', {clientX: 10, clientY: 0}));
  draggable.dispatchEvent(<any>new MouseEvent('mousemove', {clientX: coords.left + 40, clientY: coords.top + 10}));
  cy.get(dropSelector).click();
  //   draggable.dispatchEvent(new MouseEvent('mouseup'));

  return cy.get(dropSelector);
}

// Add typings for the custom command
declare global {
  namespace Cypress {
    interface Chainable {
      drag: (dragSelector: string, dropSelector: string) => Chainable;
    }
  }
}
// Finally add the custom command
Cypress.Commands.add('drag', drag);
Ulfius
  • 619
  • 7
  • 14
0

For those who are struggling with Drag and Drop and "react-beautiful-dnd" library, here is a piece of code that helped me (nothing else did). It's extracted from this post

Cypress.Commands.add('dragAndDrop', (subject, target) => {
Cypress.log({
    name: 'DRAGNDROP',
    message: `Dragging element ${subject} to ${target}`,
    consoleProps: () => {
        return {
            subject: subject,
            target: target
        };
    }
});
const BUTTON_INDEX = 0;
const SLOPPY_CLICK_THRESHOLD = 10;
cy.get(target)
    .first()
    .then($target => {
        let coordsDrop = $target[0].getBoundingClientRect();
        cy.get(subject)
            .first()
            .then(subject => {
                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');
            });
    });
});
0

for me the code from @bkucera works, thanks for that. but I had to chance 3 things in order to works for me as expected:

1.

let _to = {
              x: from.x + dx * (i),
              y: from.y - dy * (i),
            }
  1. i = i+1

  2. had to use some delay, steps and smooth:true

delay: 30,
steps: 30,
smooth: true,
Michal
  • 1
0

const dataTransfer = new DataTransfer();
cy.get('#your-item-id').trigger('dragstart', { dataTransfer });
cy.get('#your-dropable-div').trigger('drop', { dataTransfer });

Nayeem Ahmed
  • 91
  • 1
  • 5
  • This seems functionally identical to the code from https://stackoverflow.com/a/70756352/7733418 Please make your additional contribution more obvious. E.g. by .... – Yunnosch Jul 18 '22 at 14:09
  • While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – Yunnosch Jul 18 '22 at 14:09
0

Angular 14+

None of the listed solutions worked for me (Angular 14.1). It seems some newer versions of Angular Material CDK do not accept anymore the simulated drags that Cypress offers. This helped:

  1. Install cypress-real-events.
npm install --save-dev cypress-real-events

or

yarn add --dev cypress-real-events

It should be at least version 1.7.2 because of issue 234 (parameter position was ignored).

  1. add "cypress-real-events" to "compilerOptions"/"types" in cypress/tsconfig.json, e.g.
{
    "compilerOptions": {
        "target": "es5",
        "lib": ["es6", "dom"],
        "types": ["cypress", "node", "cypress-real-events"],
        "esModuleInterop": true
    },
    "include": ["**/*.ts"]
}
  1. in cypress/support/commands.ts:
import 'cypress-real-events/support';

declare global {
    namespace Cypress {
        interface Chainable {
            dragTo(dropSelector: string): void;
        }
    }
}

Cypress.Commands.add('dragTo', { prevSubject: 'element' }, function (subject, targetEl) {
    /*
     * Currently realMouseDown etc. only works in browsers based on Chromium.
     * see https://github.com/dmtrKovalenko/cypress-real-events#requirements
     */
    if (Cypress.isBrowser('firefox')) this.skip();
    /*
     * explicit scrollBehavior because default breaks some tests
     */
    cy.wrap(subject)
        .first()
        .realMouseDown({ button: 'left', position: 'center', scrollBehavior: 'nearest' })
        .realMouseMove(10, 0, { position: 'center', scrollBehavior: 'nearest' });
    cy.get(targetEl)
        .first()
        .realMouseMove(10, 0, { position: 'center', scrollBehavior: 'nearest' })
        .realMouseUp({ position: 'center', scrollBehavior: 'center' });
    /*
     * workaround for a problem where the original drag selector did work only once
     */
    cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting
});
  1. usage: cy.get('#foo').dragTo('#bar') (use whatever selectors you want)
Marcus
  • 1,857
  • 4
  • 22
  • 44
  • 3
    There is no need, the correct answer still works for me. – Vagdevi Oct 19 '22 at 09:38
  • @Vagdevi Sorry, I should have made it more clear I am talking about Angular 14 (now updated). You don't use Angular 14, do you? – Marcus Oct 20 '22 at 10:17
  • Also works for me in Angular 13 and Cypress 10. More or less the same answer as mentioned in this question here: https://stackoverflow.com/a/71968175/1697459 – Wilt Oct 25 '22 at 09:20
  • 2
    **only works in browsers based on Chromium** - we need Firefox testing, how can we get round the problem? – Florence.P Oct 28 '22 at 23:18
  • @Florence.P Good question. Maybe someone could join that cypress-real-events project and add Firefox support? – Marcus Nov 14 '22 at 10:58
0
cy.visit("/your-page-link");
cy.get("your-Selector")
     .trigger("mousedown", { button: 0 , force: true })
     .trigger("mousemove", 200, -200, { force: true })
cy.get("Target-For-Drop").click()
    .trigger("mouseup", { force: true });
0

Testing drag and drop elements sorting within a single list (Angular Material 13 + Cypress 10)

const dragDropElement = 
  (wrapperSelector, draggableSelector, droppableSelector) => {
    // a few extra pixels to get the ordering right
    const offset = 10;

    const getCoords = (elem) => {
      // top left coordinates with scroll
      // use jQuery from Cypress
      const offset = cy.$$(elem).offset();
      return {
        top: offset.top,
        left: offset.left
      };
    };

    // event data
    const createEventData = (top = 0, left = 0, data = {}) => {
      return {
        bubbles: true,
        cancelable: true,
        composed: true,
        clientX: left,
        clientY: top,
        screenX: left,
        screenY: top,
        ...data
      };
    };

    // get wrapper element (cdkdroplist)
    cy.get(wrapperSelector).then(el => {
      const wrapper = el[0];  // wrapper
      // get draggable element (cdkdrag)
      cy.get(draggableSelector).then(el => {
        const draggable = el[0];  // pick up this
        // get droppable element (cdkdrag)
        cy.get(droppableSelector).then(el => {
          const droppable = el[0];  // drop over this
          // get coordinates for picked up and dropped element
          const dragCoords = getCoords(draggable);
          const dropCoords = getCoords(droppable);
          // mouse down on picked element
          draggable.dispatchEvent(new MouseEvent('mousedown',
            createEventData(dragCoords.top + offset, dragCoords.left + offset, {
              buttons: 1, // needed to start dragging
            })
          ));
          // small mouse move with picked element
          draggable.dispatchEvent(new MouseEvent('mousemove',
            createEventData(dragCoords.left + offset * 2, dragCoords.top + offset)
          ));
          // mouse move picked element to target coordinates
          draggable.dispatchEvent(new MouseEvent('mousemove',
            createEventData(dropCoords.top + offset, dropCoords.left + offset)
          ));
          // mouse up on wrapper element with dropped element coordinates
          wrapper.dispatchEvent(new MouseEvent('mouseup',
            createEventData(dropCoords.top + offset, dropCoords.left + offset)
          ));
        });
      });
    });
  };

And call this method to move for example the third element before the first:

dragDropElement(
  '#cdk-drop-list-0',
  '#cdk-drop-list-0 > :nth-child(3)',
  '#cdk-drop-list-0 > :nth-child(1)'
  );
xhtml.ru
  • 1
  • 1
0

##. Cypress Mouse Drag and Drop a inside page scroller from Left, Right and Both side. Try the code below:

1. Drag and Drop from Left:

const dragLeftScoller = cy.leftGraphNavigator().first();

dragLeftScoller.trigger('mousedown', 100, 100, { force: true}).trigger('mousemove', 600, 600, { force: true });

dragLeftScoller.trigger('mouseup', { force: true });

enter image description here

2. Drag and Drop from Right:

const dragRightScoller =cy.rightGraphNavigator().first();

dragRightScoller.trigger('mousedown', 100, 100, { force: true }).trigger('mousemove', -30, -30, { force: true });

dragRightScoller.trigger('mouseup', { force: true });

enter image description here

3. Overlap -Drag and Drop from Left and Right : Increase and decrease the value of 1200 and -30 to adjust overlapping for your application:

const dragLeftScoller =cy.leftGraphNavigator().first();

dragLeftScoller.trigger('mousedown', 100, 100, { force: true }).trigger('mousemove', 1200, 1200, { force: true });

dragLeftScoller.trigger('mouseup', { force: true });

const dragRightScoller =cy.rightGraphNavigator().first();

dragRightScoller.trigger('mousedown', 100, 100, { force: true }).trigger('mousemove', -30, -30, { force: true });

dragRightScoller.trigger('mouseup', { force: true });

enter image description here

-1

You just need to replace the class or id from where you want to drag and drop


const dataTransfer = new DataTransfer(); 
cy.get('ID or class which you want to drag').trigger('dragstart',{
   dataTransfer 
  });
cy.get('ID or class where  you want to drop').trigger('drop',{
     dataTransfer
});