cypress-real-events icon indicating copy to clipboard operation
cypress-real-events copied to clipboard

feat: Add cy.realDnd command

Open dmtrKovalenko opened this issue 4 years ago • 7 comments

Closes #11

dmtrKovalenko avatar Dec 19 '20 13:12 dmtrKovalenko

I would love to get this landed - anything in particular that I could help with?

Andarist avatar Sep 10 '21 15:09 Andarist

I stopped in the implementation because it has some underlying problems with pupeteer itself. Just 3 actions touch -> touchMove -> touchEnd don't behave as drag and drop for some reason.

But if you will find a solution that would be perfect!

dmtrKovalenko avatar Sep 10 '21 15:09 dmtrKovalenko

I didn't yet have time to look into this yet - it seems that you have been testing this with native [draggable] element and maybe this doesn't work for some reason.

In our app, we are implementing drag & drop in a custom way, using pointer events, and a solution similar to yours works just OK for us:

Custom `cy.drag` command
import { fireCdpCommand } from 'cypress-real-events/fireCdpCommand';
import { getCypressElementCoordinates } from 'cypress-real-events/getCypressElementCoordinates';

type Point = { x: number; y: number };

const clamp = (v: number, min: number, max: number) =>
  Math.min(Math.max(v, min), max);

const getDirection = (start: number, end: number) => {
  switch (true) {
    case end > start:
      return 1;
    case end < start:
      return -1;
    default:
      return 0;
  }
};

export const drag = (
  subject: JQuery<HTMLElement>,
  options: { target: () => Cypress.Chainable<JQuery<HTMLElement>> },
) => {
  const { target } = options;
  const sourceCoordinates = getCypressElementCoordinates(subject, 'center');
  cy.wrap(subject).realMouseDown();

  return target()
    .then(async ($target) => {
      const targetCoordinates = getCypressElementCoordinates($target, 'center');

      const direction = {
        x: getDirection(sourceCoordinates.x, targetCoordinates.x),
        y: getDirection(sourceCoordinates.y, targetCoordinates.y),
      };

      const clampPoint = (point: Point): Point => ({
        x: clamp(point.x, sourceCoordinates.x, targetCoordinates.x),
        y: clamp(point.y, sourceCoordinates.y, targetCoordinates.y),
      });

      // just fire a single pointermove event very close to ther source point
      await fireCdpCommand('Input.dispatchMouseEvent', {
        type: 'mouseMoved',
        button: 'left',
        pointerType: 'mouse',
        ...clampPoint({
          x: sourceCoordinates.x + direction.x * 1,
          y: sourceCoordinates.y + direction.y * 1,
        }),
      });

      let nextPoint = { ...sourceCoordinates };

      while (true) {
        // this doesn't move in a straight line at the moment but the movement line shouldn't matter for most of our scenarios
        // we just progress at each axis with a 10px step until we reach the target point on a given axis
        // when target points are met on both axes then we exit the "movement" phase
        nextPoint = clampPoint({
          x: nextPoint.x + direction.x * 10,
          y: nextPoint.y + direction.y * 10,
        });

        if (
          nextPoint.x === targetCoordinates.x &&
          nextPoint.y === targetCoordinates.y
        ) {
          break;
        }

        await fireCdpCommand('Input.dispatchMouseEvent', {
          type: 'mouseMoved',
          button: 'left',
          pointerType: 'mouse',
          ...nextPoint,
        });
      }

      return $target;
    })
    .then(($target) => {
      cy.wrap($target).realMouseUp();
      return $target;
    });
};

So maybe even if this doesn't work for [draggable] it would still be OK to land this feature with a documentation note about this caveat?

I see that there is an experimental Input.dispatchDragEvent command and maybe this is supposed to be used to emulate drag on [draggable] elements?

Andarist avatar Dec 02 '21 12:12 Andarist

Btw. based on this test: https://github.com/microsoft/playwright/blob/d70e37de80f99c3fe953921abd5ad7ba1a7f1da8/tests/page/page-drag.spec.ts#L34-L51 and this fixture: https://github.com/microsoft/playwright/blob/d70e37de80f99c3fe953921abd5ad7ba1a7f1da8/tests/assets/drag-n-drop.html it looks like it should work as this is using roughly the same stuff under the hood: https://github.com/microsoft/playwright/blob/1bfc473bc8b88a708c29bfab2bb26c2efc3b2706/packages/playwright-core/src/server/chromium/crInput.ts#L99-L137

However, the stuff here is wrapped with the drag manager that dispatched explicit drag commands to Chrome: https://github.com/microsoft/playwright/blob/1bfc473bc8b88a708c29bfab2bb26c2efc3b2706/packages/playwright-core/src/server/chromium/crDragDrop.ts#L28

Mouse moves are actually wrapped with drag detection here: https://github.com/microsoft/playwright/blob/1bfc473bc8b88a708c29bfab2bb26c2efc3b2706/packages/playwright-core/src/server/chromium/crInput.ts#L99-L109

So I guess somewhat similar shenanigans are needed to enable this here. It seems that the mouse move event itself starts the drag session but the session itself has to be managed manually~ with explicit drag commands.

Andarist avatar Dec 03 '21 09:12 Andarist

Yes please! Or can we have a realMouseMove if this is not the way to go?

knownasilya avatar Feb 02 '22 13:02 knownasilya

Hey @dmtrKovalenko ! Please let me know if there's anything I can help with on this one or with realMouseMove ! Tested mouse move on my PR, working great.

DolevBitran avatar Feb 02 '22 15:02 DolevBitran

@drecali any interest in picking this up? I see you're contributing to realMouseMove recently.

JessicaSachs avatar Oct 28 '22 00:10 JessicaSachs