How to implement Selenium test cases for canvas

Hi,
We want to write selenium test cases on gojs diagram, which includes basic events like
a. clicking on a node/link
b. double click on a node/link
c. right click on a node/link
d. selecting a node/link.

As gojs is canvas based approach, Didn’t found any way to get the DOM elements of diagram elements.
But Using JavascriptExecutor we are able to get the elements from the diagram.
and able to perform select operation on a node/link, but not able to do remaining.
What’s the way to perform click, rightclick and doubleclick on nodes/links.

Our current approach is:

private WebDriver driver;

public void testGoJS() throws Exception {
// opening a webpage, which contains gojs diagram
driver.get(“Basic GoJS Sample”);
if (driver instanceof JavascriptExecutor) {
<span =“Apple-tab-span” style=“white-space:pre”> String code = "var key = myDiagram.model.nodeDataArray[1].key;
var node = myDiagram.findNodeForKey(key);
myDiagram.select(node) ";
<span =“Apple-tab-span” style=“white-space:pre”> ((JavascriptExecutor) driver).executeScript(code);
<span =“Apple-tab-span” style=“white-space:pre”>
<span =“Apple-tab-span” style=“white-space:pre”> }
}

Is there any better approach for selenium ?

Yes, the whole point of using Canvas is to avoid the overhead of dealing with DOM elements.

I think you have the right idea. What I think you don’t know how to do is how to simulate mouse and keyboard events.

We implemented our own test system years ago. In our “Test” class we defined a number of handy methods for simulating mouse and keyboard input. Here’s our code:
NOTE: this is obsolete – use the Robot class defined in extensions/Robot.js

[code]
/**

  • Simulate a mouse down event.
  • @this {Test}
  • @param {Point} pt a point in document coordinates.
  • @param {object=} eventprops an optional argument providing properties for the InputEvent.
    */
    Test.prototype.mouseDown = function (pt, eventprops) {
    var diagram = this.diagram;
    var n = new go.InputEvent();
    n.diagram = diagram;
    n.documentPoint = pt;
    n.viewPoint = diagram.transformDocToView(pt);
    n.down = true;
    if (eventprops) {
    for (var p in eventprops) {
    n[ p ] = eventprops[ p ];
    }
    }
    diagram.lastInput = n;
    diagram.firstInput = n.copy();
    diagram.currentTool.doMouseDown();
    };

/**

  • Simulate a mouse move event.
  • @this {Test}
  • @param {Point} pt a point in document coordinates.
  • @param {object=} eventprops an optional argument providing properties for the InputEvent.
    */
    Test.prototype.mouseMove = function (pt, eventprops) {
    var diagram = this.diagram;
    var n = new go.InputEvent();
    n.diagram = diagram;
    n.documentPoint = pt;
    n.viewPoint = diagram.transformDocToView(pt);
    if (eventprops) {
    for (var p in eventprops) {
    n[ p ] = eventprops[ p ];
    }
    }
    diagram.lastInput = n;
    diagram.currentTool.doMouseMove();
    };

/**

  • Simulate a mouse up event.
  • @this {Test}
  • @param {Point} pt a point in document coordinates.
  • @param {object=} eventprops an optional argument providing properties for the InputEvent.
    */
    Test.prototype.mouseUp = function (pt, eventprops) {
    var diagram = this.diagram;
    var n = new go.InputEvent();
    n.diagram = diagram;
    n.documentPoint = pt;
    n.viewPoint = diagram.transformDocToView(pt);
    n.up = true;
    if (diagram.firstInput.documentPoint.equals(pt)) n.clickCount = 1; // at least??
    if (eventprops) {
    for (var p in eventprops) {
    n[ p ] = eventprops[ p ];
    }
    }
    diagram.lastInput = n;
    diagram.currentTool.doMouseUp();
    };

/**

  • Simulate a mouse wheel event.
  • @this {Test}
  • @param {number} delta non-zero turn
  • @param {object=} eventprops an optional argument providing properties for the InputEvent.
    */
    Test.prototype.mouseWheel = function (delta, eventprops) {
    var diagram = this.diagram;
    var n = diagram.lastInput.copy();
    n.diagram = diagram;
    n.delta = delta;
    if (eventprops) {
    for (var p in eventprops) {
    n[ p ] = eventprops[ p ];
    }
    }
    diagram.lastInput = n;
    diagram.currentTool.doMouseWheel();
    };

/**

  • Simulate a key down event.
  • @this {Test}
  • @param {string|number} keyorcode
  • @param {object=} eventprops an optional argument providing properties for the InputEvent.
    */
    Test.prototype.keyDown = function (keyorcode, eventprops) {
    var diagram = this.diagram;
    var n = diagram.lastInput.copy();
    n.diagram = diagram;
    if (typeof(keyorcode) === ‘string’) {
    n.key = keyorcode;
    } else if (typeof(keyorcode) === ‘number’) {
    n.key = String.fromCharCode(keyorcode);
    }
    n.down = true;
    if (eventprops) {
    for (var p in eventprops) {
    n[ p ] = eventprops[ p ];
    }
    }
    diagram.lastInput = n;
    diagram.currentTool.doKeyDown();
    };

/**

  • Simulate a key up event.
  • @this {Test}
  • @param {string|number} keyorcode
  • @param {object=} eventprops an optional argument providing properties for the InputEvent.
    */
    Test.prototype.keyUp = function (keyorcode, eventprops) {
    var diagram = this.diagram;
    var n = diagram.lastInput.copy();
    n.diagram = diagram;
    if (typeof(keyorcode) === ‘string’) {
    n.key = keyorcode;
    } else if (typeof(keyorcode) === ‘number’) {
    n.key = String.fromCharCode(keyorcode);
    }
    n.up = true;
    if (eventprops) {
    for (var p in eventprops) {
    n[ p ] = eventprops[ p ];
    }
    }
    diagram.lastInput = n;
    diagram.currentTool.doKeyUp();
    };[/code]
    This allows us to write tests that perform actions like: function(test) { // execute the test test.mouseDown(new go.Point(60, 10), { timestamp: 0 }); // below/right of Alpha location test.mouseMove(new go.Point(75, -25), { timestamp: 100 }); // between down and up points test.mouseUp(new go.Point(100, -50), { timestamp: 200 }); // mouse up point }
    The test could then check: function(test) { // check the results test.assert(test.diagram.selection.count === 1, "should have selected one node"); var alpha = test.diagram.findNodeForKey("Alpha"); test.assert(test.diagram.selection.first() === alpha, "selected Node isn't Alpha"); test.assert(test.isApproxPoint(alpha.location, new go.Point(90, -60)), "didn't move Alpha to 90,-60"); }
    So you could do similar things in your environment. The advantage of this is that everything is in document coordinates, so it is more resistant to irrelevant changes in the DOM.

HOWEVER:

However, in our tests we infrequently make use of mouse or keyboard events. Instead, most of our tests deal directly with the model and with Parts in the Diagram. This is what you are doing by calling Diagram.select, rather than pretending to click at a certain point in order to select a Node. You can still check that a Node is at a particular location (but remember not to compare floating point numbers with equality!). But be careful about checking for sizes, especially if the size is dependent on the measurement of TextBlocks, since those can be different on different platforms.

Hi Walter, Thanks for the reply that’s quite informative.

Actually we are using ParallelLayout to layout the diagram.
And our diagram contains nodes and links, each node and link having their own functionality.

Using JavascriptExecuter(as mentioned in the post) we are able to get the node and links from diagram.
Now we want to perform doubleclick and rightclick on these node/links.

Is there any such methods like node.contextClick() and node.doubleClick(), so that it can directly do our job.
or any alternative approach.

Hey Walter,

In continuation to the point raised by Srinivas:

We are now able to simulate single click and double click using Input Event Object with clickCount set appropriately.
Now we are stuck with context click. For it we tried to create a DiagramEvent object, but not sure how to set the mousePoint which is used by the ContextTool.
Are we on the right track? If so what are we missing. Otherwise what approach to take?




The reasons there are no methods like Node.doubleClick are several fold:

  • the behavior might be different at different points in the node because GraphObjects within the node’s visual tree might define their own click behavior (example: context click on ports in the Dynamic Ports sample)
  • there are other expected side-effects besides just calling the node’s GraphObject.click (or doubleClick or contextClick) event handler function, including selection and raising DiagramEvents
  • the node might not be visible, for any number of reasons, so users would not be able to click on the node, making your test less realistic than it could be

The benefit of actually simulating InputEvents is that it goes through the actual process of choosing and running tools. So it will actually start the ContextMenuTool or ClickSelectingTool, as appropriate.


Our internal tests that exercise ContextMenuTool do things like:
      test.mouseDown(new go.Point(100, 100), { timestamp: 0, button: 2 });Â  // right mouse button on node N3<br />      test.mouseUp(new go.Point(100, 100), { timestamp: 100, button: 2 });

This is followed by a wait for the diagram to be updated to show the context menu, and then:

      test.mouseDown(new go.Point(135, 110), { timestamp: 1000 });Â  // left click on first context menu button<br />      test.mouseUp(new go.Point(135, 110), { timestamp: 1200 });

Â

Hi walter,
By using earlier mouseDown and mouseUp we are able to achieve left click operation.
Now to implement right click, we just changed

diagram.currentTool.doMouseDown(); to diagram.currentTool.contextMenuTool.doMouseDown();
and
diagram.currentTool.doMouseUp(); to diagram.currentTool.contextMenuTool.doMouseUp();
in both mouseDown and mouseUp.
Still the right click is not happening.
Are we missing something else ??

But that doesn’t cause the ContextMenuTool to run.
You didn’t try code like what I quoted earlier today?

Thanks walter, now I got it.
Initially we ignored event properties, now using “button:2” as event property we are able to do it.

Is there a drag and drop routine which can be used to drag and drop from Palette to a Diagram?

mouseDown -> mouseMove -> mouseUp sequence is not dropping the node on the target.

No, the above code for simulating input events assumes there is only one Diagram. We can investigate extending that functionality to cover dragging between diagrams.

For the next release, 1.3.4, there will be a new Robot class in extensions/Robot.js which will include the functionality shown above but with extensions for supporting drag-and-drop between Diagrams.

There will be a corresponding extensions/Robot.html sample to demonstrate how to simulate input events for testing purposes.

Thank you for your response.

Our team has resolved this using webdriver API. Sharing it so any selenium user may benefit.
It seems the palette needs to be made aware of a drag within its’ viewport.

    Actions actions = new Actions(SeleniumTestBase.getBrowserDriver());
    actions.moveToElement(paletteCanvas, palette_node_x, palette_node_y);
    actions.clickAndHold();
    //Dragging selected node a little bit to make it work. 
    actions.moveToElement(paletteCanvas, palette_node_x, palette_node_y+50 );
    //Now perform the actual move
    actions.moveToElement(flowCanvas, diagramOffsetX , diagramOffsetY);
    actions.release();
    actions.perform();

Thanks for the additional information!

Hi Sanojantony and Walter,

I am trying to click my image cards created on canvas but I am unable to achieve it . I am using below snippet:
Actions actions = new Actions(getDriver());
actions.moveToElement(element).moveByOffset(x, y).click().build();
actions.perform();

I am using below values f:
element= driver.findElement(By.xpath("//div[@id=‘bigRoomPlanning’]/canvas"));
x=143, y=50

Can you please help me?

We do not have any experience using Selenium.

@sanojantony - can you please tell how you able to find exact x and y for particular control?

@prashantsingh98 - Did you able to automate it?

@sanojantony Can you please let me know how to find x and Y axis for specific controls?

Hi aditya,
any success here.

Hi Walter and Sanojantony ,
I am trying to drag and drop the objects & nodes on the canvas for Gojs application as it’s unable to perform the action. What’s the way to perform the action to drag & drop the objects & nodes into the canvas. I am using below snippet:

Actions act = new Actions(driver);

    WebElement src  = driver.findElement(By.xpath("/html/body/dfm-root/dfm-main-container/nb-layout/div/div/div/div/div/nb-layout-column/dfm-tabs-container/div/div/div[2]/dfm-view-diagram-container/div/dfm-diagram-sidebar/div/div[1]/dfm-diagram-tabs-container/div/div[2]/dfm-palettes-diagram-container/div/div[2]/dfm-go-js-add-object-palette/div/div/canvas"));
    
    WebElement target  = driver.findElement(By.xpath("/html/body/dfm-root/dfm-main-container/nb-layout/div/div/div/div/div/nb-layout-column/dfm-tabs-container/div/div/div[2]/dfm-view-diagram-container/div/div/dfm-go-js-display-diagram/div/div"));

// Create the JavascriptExecutor object
JavascriptExecutor js=(JavascriptExecutor)driver;

    // find element using xpath attribute
      WebElement link = driver.findElement(By.xpath("/html/body/dfm-root/dfm-main-container/nb-layout/div/div/div/div/div/nb-layout-column/dfm-tabs-container/div/div/div[2]/dfm-view-diagram-container/div/dfm-diagram-sidebar/div/div[1]/dfm-diagram-tabs-container/div/div[2]/dfm-palettes-diagram-container/div/div[1]/dfm-go-js-links-palette/div"));
    
      // call the executeScript method
      js.executeScript("arguments[0].setAttribute('style', 'background: yellow; border: 2px solid red;');", link);
      
    var canvas_dim = src.getSize();
    var canvas_centre_x = canvas_dim.getWidth() / 2;
    var canvas_centre_y = canvas_dim.getHeight() / 2;
    int button_x = (canvas_centre_x / 3) * 2;
    int button_y = (canvas_centre_y / 3) * 2;
    
    
    act.moveToElement(target,button_x  , button_y)
    .click().perform();

`
Can you please help me out?