Search code examples
javascriptdrag-and-dropwebdrivercodeceptjs

How to implement drag and drop (drag to reorder) with CodeceptJs


I have this request to implement drag and drop between two cards so they swap their positions, so more specifically a "drag to reorder". Not sure how to tackle this scenario considering the container would not be empty when I’m dropping the new item into it? I see examples for Selenium and Protractor but can't find ones for CodeceptJs.

I tried this here

await I.dragAndDrop('//*[@id="myAssessments"]/main/div/div[7]/div[2]/div[1]/li', '//*[@id="myAssessments"]/main/div/div[7]/div[2]',);

which I believe to be the equivalent to

I.dragAndDrop('#dragHandle', '#container');

from https://codecept.io/helpers/WebDriver/#draganddrop using the xpath for the container and the one for the first list item but the element did not drag and I got the error Element "{null: undefined}" was not found by text|CSS|XPath enter image description here


Solution

  • We got it working now. We needed to use a custom function.

    It seems as perhaps due to the fact that our elements have rounded corners, the 0,0 position is not being reached, so we need to try to get the middle for the element.

    Therefore, this is what we needed to do.

    await I.dragDown(
      {
        css:
          '#myAssessments > main > div > * > div:nth-child(2) > div:nth-child(1)',
      },
      200,
    ); 
    
    async dragDown(locator, pixels) {
      await this.dragInDirection('down', locator, pixels);
    }
    
    async dragInDirection(direction, locator, pixels) {
      const queryString = resolveLocator(locator);
      const location = await this.getElementLocation(queryString);
      const x = parseInt(location.x + location.width / 2, 10);
      const y = parseInt(location.y + location.height / 2, 10);
      await this.performBrowserActions([
        createActionSet({ x, y }, direction, pixels),
      ]);
    }
    
    function resolveLocator(locator) {
      let queryString;
      if (locator.dataTestId) {
        queryString = `[data-testid=${locator.dataTestId}]`;
      } else if (locator.test) {
        queryString = `[data-test-id=${locator.test}]`;
      } else if (locator.css) {
        queryString = locator.css;
      } else {
        queryString = locator;
      }
      return queryString;
    }
    
    function createActionSet(origin, direction, length) {
     const { x, y } = origin;
    
     let xMoveTo = 0;
     let yMoveTo = 0;
    
     switch (direction) {
        case 'up':
          yMoveTo = -length;
          break;
        case 'down':
          yMoveTo = length;
          break;
        case 'right':
          xMoveTo = length;
          break;
        case 'left':
          xMoveTo = -length;
          break;
        default:
          return new Error('Direction passed was wrong or no direction passed');
      }
    
    return {
        type: 'pointer',
        id: 'finger1',
        parameters: { pointerType: 'touch' },
        actions: [
          { type: 'pointerMove', duration: 0, x, y },
          { type: 'pointerDown', button: 0 },
          { type: 'pause', duration: 500 },
          {
            type: 'pointerMove',
            duration: 1000,
            origin: 'pointer',
            x: xMoveTo,
            y: yMoveTo,
          },
          { type: 'pointerUp', button: 0 },
        ],
      };
    }
    

    If not using custom locators you can perhaps simplify and do it like this:

    async dragInDirection(direction, locator, pixels) {
      const location = await this.getElementLocation(locator);
      const x = parseInt(location.x + location.width / 2, 10);
      const y = parseInt(location.y + location.height / 2, 10);
      await this.performBrowserActions([
        createActionSet({ x, y }, direction, pixels),
      ]);
    }
    

    And here is the version with keyboard tabbing:

    await I.pressKey('Tab');
    await I.pressKey('Tab'); // Tabbing to focus the card
    await I.pressKey('Space'); // "Jump" item
    await I.pressKey('ArrowDown'); // Move item
    await I.pressKey('Space'); // Drop item