2

For the past couple of months, I've been developing a workflow modeler for the final stage of my SO-driven SE education. I've been using mxGraph and vanilla javascript and i tried setting up some basic E2E tests using Cypress.

I encountered some problems with (I'm assuming) triggering the event listeners. Some buttons DO respond to cypress clicks/triggers and some DONT. None of the buttons in my application have a onClick actions or any other attribute that contains a model or controller method. Instead all buttons and keys have handlers and listeners, created by the mxGraph-editor utility class.

I've tried to recreate some of the actions using the same E2E tests on the public examples of mxGraph(see cypress code). The dragging of an object (from menu to canvas (#1) & from canvas to canvas(#4)) AND the selecting of an object(#2) sadly don't work.

The double clicking on an object and modifying the text(#3) DOES work however... and I'm lost. I've tried all the different ways of forcing, waiting, clicking and triggering but all to no avail.

describe('mxGraph "ports" example', function () {

    it('Start ports example', function () {
        cy.visit('https://jgraph.github.io/mxgraph/javascript/examples/ports.html')
        cy.wait(500)
    })
    // Example #1 : FAIL
    it('#1 Create new object by dragging', function () {
        cy.get('div[id="sidebarContainer"]').find('img[title="Drag this to the diagram to create a new vertex"]').first()
           .trigger('mousedown', { force: true})
            .trigger('mousemove', { clientX: 250, clientY: 250, force: true})
            .trigger('mouseup', {force: true})
        cy.get('div[id="graphContainer"]').find('svg').trigger('drop', { force: true })
        cy.wait(500)
    })
})
describe('mxGraph "user object" example', function () {

    it('Start userobject example', function () {
        cy.visit('https://jgraph.github.io/mxgraph/javascript/examples/userobject.html')
        cy.wait(500)
    })
    // Example #2 : FAIL
    it('#2 Single click on object (green highlight should appear)', function () {
        cy.get('rect').first().click({ force: true })
        cy.wait(500)
    })
    // Example #3 : PASS
    it('#3 Double click & edit object (Text should be modified)', function () {
        cy.get('rect').first().dblclick({ force: true })
        cy.wait(500)
        cy.get('div [class="mxCellEditor mxPlainTextEditor"]').first().type('text modified')
        cy.wait(500)
    })
    // Example #4 : FAIL
    it('#4 Drags object to center of canvas (Object should be moved)', function () {
        cy.get('rect').first()
            .trigger('mousedown', { force: true})
            .trigger('mousemove', { clientX: 250, clientY: 250, force: true})
            .trigger('mouseup', {force: true})
        cy.wait(500)
    })
})

Any help is greatly appreciated. Thanks in advance!

Kenny
  • 47
  • 2
  • 7
  • 1
    Drag and drop is done like: https://docs.cypress.io/api/commands/trigger.html#jQuery-UI-Sortable - A more complete example: https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/testing-dom__drag-drop/cypress/integration/drag_n_drop_spec.js – Lawrence Cherone Oct 07 '19 at 22:58
  • Thx, i modified test #4 to resemble the example but without luck. I can see that Cypress presses the mouse key, but it doesnt move the mouse or release the dragged object. Also the single click in test #2 does NOT work, but the double click in test #3 DOES work... – Kenny Oct 08 '19 at 10:43

1 Answers1

2

Your app seems to be using pointer events, so try replacing mouse with pointer in all those event names.

Also, here's a more complete abstraction around drag-and-drop:


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)

    if (type.includes('drag') || type === 'drop') {
      return el.dispatchEvent(
        new Event(type, Object.assign({}, { clientX: coords.x, clientY: coords.y }, { bubbles: true, cancelable: true }))
      )
    }

    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('pointerover', from, fromEl)
    send('mousedown', from, fromEl)
    send('pointerdown', from, fromEl)
    send('dragstart', 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('pointermove', to, fromEl)
        send('drag', to, fromEl)
        send('mouseover', to)
        send('pointerover', to)
        send('dragover', to)
        send('mousemove', to)
        send('pointermove', to)
        send('drag', to)
        send('mouseup', to)
        send('pointerup', to)
        send('dragend', to)
        send('drop', to)

        _log.snapshot('after', { at: 1 }).end()

      })

    })

  }

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

  drag(fromCoords, toCoords, opts.steps)
}

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
}

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

Here's how you'd use it:

describe('mxGraph "ports" example', function () {
  beforeEach(() => {
    cy.visit('https://jgraph.github.io/mxgraph/javascript/examples/ports.html')
    cy.get('div#splash').should('not.be.visible')
  })

  // Example #1 : FAIL
  it('#1 Create new object by dragging', function () {
    cy.get('div[id="sidebarContainer"]').find('img[title="Drag this to the diagram to create a new vertex"]').first()
    .dragTo('div[id="graphContainer"]')
  })
})
kuceb
  • 16,573
  • 7
  • 42
  • 56