Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to simulate a drag and drop action in puppeteer?

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?

like image 736
Khushi Avatar asked Apr 11 '18 10:04

Khushi


4 Answers

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();
like image 183
Grant Miller Avatar answered Oct 23 '22 09:10

Grant Miller


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.

like image 27
alex Avatar answered Oct 23 '22 09:10

alex


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.

like image 5
Moshisho Avatar answered Oct 23 '22 10:10

Moshisho


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' });
like image 2
ollix Avatar answered Oct 23 '22 10:10

ollix