21

I have React-DnD(Drag and drop) in my application. I'd like test it E2E.

What I want to simulate is drag a particular element and drop to a particular place. How do I do this?

What I have is:

//test.js
const mouse = page.mouse;
await mouse.down();
await mouse.move(126, 19);
await page.waitFor(400);

Using this code, selection is done but drag is not working. How should I implement this?

Moshisho
  • 2,781
  • 1
  • 23
  • 39
Khushi
  • 1,759
  • 6
  • 26
  • 45

4 Answers4

17

The following method will allow you to simulate a drag-and-drop action in Puppeteer:

const example = await page.$('#example');
const bounding_box = await example.boundingBox();

await page.mouse.move(bounding_box.x + bounding_box.width / 2, bounding_box.y + bounding_box.height / 2);
await page.mouse.down();
await page.mouse.move(126, 19);
await page.mouse.up();
Grant Miller
  • 27,532
  • 16
  • 147
  • 165
9

None of the Puppeteer specific solutions worked for me, so I ended up writing native javascript to a file and imported that into Puppeteer (and in my case, Jest).

drag-and-drop.js

async function dragAndDrop(source, target) {
  await page.evaluate((source, target) => {
    source = document.querySelector('#'+source);

    event = document.createEvent("CustomEvent");
    event.initCustomEvent("mousedown", true, true, null);
    event.clientX = source.getBoundingClientRect().top;
    event.clientY = source.getBoundingClientRect().left;
    source.dispatchEvent(event);

    event = document.createEvent("CustomEvent");
    event.initCustomEvent("dragstart", true, true, null);
    event.clientX = source.getBoundingClientRect().top;
    event.clientY = source.getBoundingClientRect().left;
    source.dispatchEvent(event);

    event = document.createEvent("CustomEvent");
    event.initCustomEvent("drag", true, true, null);
    event.clientX = source.getBoundingClientRect().top;
    event.clientY = source.getBoundingClientRect().left;
    source.dispatchEvent(event);


    target = document.querySelector('#'+target);

    event = document.createEvent("CustomEvent");
    event.initCustomEvent("dragover", true, true, null);
    event.clientX = target.getBoundingClientRect().top;
    event.clientY = target.getBoundingClientRect().left;
    target.dispatchEvent(event);

    event = document.createEvent("CustomEvent");
    event.initCustomEvent("drop", true, true, null);
    event.clientX = target.getBoundingClientRect().top;
    event.clientY = target.getBoundingClientRect().left;
    target.dispatchEvent(event);

    event = document.createEvent("CustomEvent");
    event.initCustomEvent("dragend", true, true, null);
    event.clientX = target.getBoundingClientRect().top;
    event.clientY = target.getBoundingClientRect().left;
    target.dispatchEvent(event);
  }, source, target);
}

test.js

const dragAndDrop = require('./drag-and-drop')

describe('when dragging and dropping todo', () => {

  it('should change order on DOM', async () => {
    const firstTodo = await page.evaluate(() => document.querySelectorAll('.input-container .input')[0].id);
    const secondTodo = await page.evaluate(() => document.querySelectorAll('.input-container .input')[1].id);

    dragAndDrop(firstTodo, secondTodo);

    const newFirstTodo = await page.evaluate(() => document.querySelectorAll('.input-container .input')[0].id);
    const newSecondTodo = await page.evaluate(() => document.querySelectorAll('.input-container .input')[1].id);

    expect(newFirstTodo).toEqual(secondTodo)
    expect(newSecondTodo).toEqual(firstTodo)
  });
});

Slightly more work than the built-in Puppeteer functions, but hopefully this is an easy enough copy and paste solution for anyone else that needs more control over dragging and dropping.

alex
  • 1,042
  • 4
  • 18
  • 33
  • 2
    clientX and clientY are swapped vis a vis getBoundingClientRect().top and left, but once I fixed that, this worked for me, thank you!! – Rego Sen May 12 '20 at 22:33
  • 1
    perfect it works, only missing thing is the dataTransfer object on the event – Lk77 Jun 29 '21 at 15:51
5

Here's a snippet I'm using for drag and drop in puppeteer:

// This assumes only one element will be found for selectors you 
// provide, otherwise there's ambiguity on which element was selected.
// In my code I'm throwing on more than 1 element found

async function dragAndDrop(page, originSelector, destinationSelector) {
  const origin = await page.waitForSelector(originSelector)
  const destination = await page.waitForSelector(destinationSelector)
  const ob = await origin.boundingBox()
  const db = await destination.boundingBox()
    
  console.log(`Dragging from ${ob.x + ob.width / 2}, ${ob.y + ob.height / 2}`)
  await page.mouse.move(ob.x + ob.width / 2, ob.y + ob.height / 2)
  await page.mouse.down()
  console.log(`Dropping at   ${db.x + db.width / 2}, ${db.y + db.height / 2}`)
  await page.mouse.move(db.x + db.width / 2, db.y + db.height / 2)
  await page.mouse.up()
}

Note that support of drag and drop in puppeteer is still not fully implemented.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Moshisho
  • 2,781
  • 1
  • 23
  • 39
3

If the examples are not working for you, it might be another timing problem. I played around for a while, until this worked for me. Note the 50ms waits between the mouse commands and the increased number of steps, that generate more move/drag events and seem to make a difference.

// drag node
let elm = await (await page.waitForSelector("#mynode", { visible: true }));
let bounding_box = await elm.boundingBox();
let x = bounding_box.x + bounding_box.width / 2;
let y = bounding_box.y + bounding_box.height / 2;
await page.mouse.move(x, y);
await page.mouse.down();
await page.waitForTimeout(50);
await page.mouse.move(x + 100, y, { steps: 10 });
await page.waitForTimeout(50);
await page.mouse.up();
await page.waitForTimeout(50);

await page.screenshot({ path: 'after-drag.png' });
ggorlen
  • 44,755
  • 7
  • 76
  • 106
ollix
  • 41
  • 6