2  * ============LICENSE_START=======================================================
 
   3  *  Copyright (C) 2021 Nordix Foundation
 
   4  *  ================================================================================
 
   5  *  Licensed under the Apache License, Version 2.0 (the "License");
 
   6  *  you may not use this file except in compliance with the License.
 
   7  *  You may obtain a copy of the License at
 
   9  *        http://www.apache.org/licenses/LICENSE-2.0
 
  10  *  Unless required by applicable law or agreed to in writing, software
 
  11  *  distributed under the License is distributed on an "AS IS" BASIS,
 
  12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  13  *  See the License for the specific language governing permissions and
 
  14  *  limitations under the License.
 
  16  *  SPDX-License-Identifier: Apache-2.0
 
  17  *  ============LICENSE_END=========================================================
 
  20 package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
 
  22 import static org.onap.sdc.backend.ci.tests.datatypes.enums.UserRoleEnum.DESIGNER;
 
  24 import com.aventstack.extentreports.Status;
 
  25 import com.google.gson.JsonObject;
 
  26 import com.google.gson.JsonParser;
 
  27 import java.time.Duration;
 
  28 import java.util.ArrayList;
 
  29 import java.util.HashSet;
 
  30 import java.util.Optional;
 
  31 import java.util.Random;
 
  33 import lombok.AllArgsConstructor;
 
  35 import org.apache.commons.lang3.tuple.ImmutablePair;
 
  36 import org.onap.sdc.backend.ci.tests.utils.general.AtomicOperationUtils;
 
  37 import org.onap.sdc.frontend.ci.tests.datatypes.CanvasNodeElement;
 
  38 import org.onap.sdc.frontend.ci.tests.exception.CompositionCanvasRuntimeException;
 
  39 import org.onap.sdc.frontend.ci.tests.execute.setup.ExtentTestActions;
 
  40 import org.onap.sdc.frontend.ci.tests.flow.exception.UiTestFlowRuntimeException;
 
  41 import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
 
  42 import org.openecomp.sdc.be.model.ComponentInstance;
 
  43 import org.openecomp.sdc.be.model.Resource;
 
  44 import org.openecomp.sdc.be.model.Service;
 
  45 import org.openqa.selenium.Dimension;
 
  46 import org.openqa.selenium.JavascriptExecutor;
 
  47 import org.openqa.selenium.Point;
 
  48 import org.openqa.selenium.WebDriver;
 
  49 import org.openqa.selenium.WebElement;
 
  50 import org.openqa.selenium.interactions.Actions;
 
  51 import org.slf4j.Logger;
 
  52 import org.slf4j.LoggerFactory;
 
  54 public class CompositionCanvasComponent extends AbstractPageObject {
 
  56     private static final Logger LOGGER = LoggerFactory.getLogger(CompositionCanvasComponent.class);
 
  57     private static final String nodePositionJs = "var cy = window.jQuery('.sdc-composition-graph-wrapper').cytoscape('get');%n"
 
  58         + "var n = cy.nodes('[name=\"%s\"]');%n"
 
  59         + "var nPos = n.renderedPosition();%n"
 
  60         + "return JSON.stringify({%n"
 
  65     private static final String getNodesJs = "var cy = window.jQuery('.sdc-composition-graph-wrapper').cytoscape('get');\n"
 
  67         + "cy.nodes().forEach((node) => {nodes.push(JSON.stringify({name: node.data('name'), position: node.renderedPosition()}))});\n"
 
  70     private final CompositionElementsComponent compositionElementsComponent;
 
  71     private final CompositionDetailSideBarComponent compositionDetailSideBarComponent;
 
  73     private WebElement canvasWebElement;
 
  74     private Set<CanvasNodeElement> canvasElementList;
 
  75     private int canvasCenterX;
 
  76     private int canvasCenterY;
 
  77     private int canvasWidth;
 
  78     private int canvasHeight;
 
  80     public CompositionCanvasComponent(final WebDriver webDriver) {
 
  82         compositionElementsComponent = new CompositionElementsComponent(webDriver);
 
  83         compositionDetailSideBarComponent = new CompositionDetailSideBarComponent(webDriver);
 
  87     public void isLoaded() {
 
  88         //waiting the canvas data to be load and animation finishes.
 
  89         new Actions(webDriver).pause(Duration.ofSeconds(2)).perform();
 
  90         canvasWebElement = waitToBeClickable(XpathSelector.CANVAS_ELEMENT.getXpath());
 
  91         compositionElementsComponent.isLoaded();
 
  92         compositionDetailSideBarComponent.isLoaded();
 
  97     private void loadCanvas() {
 
  98         canvasWidth = canvasWebElement.getSize().getWidth();
 
  99         canvasHeight = canvasWebElement.getSize().getHeight();
 
 100         canvasCenterX = canvasWidth / 2;
 
 101         canvasCenterY = canvasHeight / 2;
 
 102         LOGGER.debug("Canvas with size [{}, {}] and center [{}, {}]", canvasWidth, canvasHeight, canvasCenterX, canvasCenterY);
 
 105     private void loadElements() {
 
 106         canvasElementList = new HashSet<>();
 
 107         final Object nodeListObj = ((JavascriptExecutor) webDriver).executeScript(getNodesJs);
 
 108         if (!(nodeListObj instanceof ArrayList)) {
 
 111         final ArrayList<String> nodeList = (ArrayList<String>) nodeListObj;
 
 112         if (nodeList.isEmpty()) {
 
 115         nodeList.forEach(nodeString -> {
 
 116             final JsonObject node = new JsonParser().parse(nodeString).getAsJsonObject();
 
 117             final JsonObject position = node.get("position").getAsJsonObject();
 
 118             final CanvasNodeElement canvasElement =
 
 119                 new CanvasNodeElement(node.get("name").getAsString(), position.get("x").getAsInt(), position.get("y").getAsInt());
 
 120             canvasElementList.add(canvasElement);
 
 124     public void selectNode(final String elementName) {
 
 125         final Optional<CanvasNodeElement> canvasElementOptional = canvasElementList.stream()
 
 126             .filter(canvasNodeElement -> canvasNodeElement.getName().startsWith(elementName))
 
 128         if (canvasElementOptional.isEmpty()) {
 
 129             throw new CompositionCanvasRuntimeException(String.format("Given element '%s' does not exist on the element list", elementName));
 
 131         final CanvasNodeElement canvasNodeElement = canvasElementOptional.get();
 
 132         final Point positionFromCenter = calculateOffsetFromCenter(canvasNodeElement.getPositionX(),
 
 133             canvasNodeElement.getPositionY());
 
 134         final Actions actions = new Actions(webDriver);
 
 135         int offsetFromElementCenter = 10;
 
 136         actions.moveToElement(canvasWebElement, positionFromCenter.getX() - offsetFromElementCenter,
 
 137             positionFromCenter.getY() + offsetFromElementCenter)
 
 138             .pause(Duration.ofSeconds(1))
 
 141         ExtentTestActions.takeScreenshot(Status.INFO, "canvas-node-selected", String.format("'%s' was selected", elementName));
 
 144     public ComponentInstance createNodeOnServiceCanvas(final String serviceName, final String serviceVersion, final String resourceName,
 
 145                                                        final String resourceVersion) {
 
 146         final Point freePositionInCanvas = getFreePositionInCanvas(20);
 
 147         final Point pointFromCanvasCenter = calculateOffsetFromCenter(freePositionInCanvas);
 
 149             final Service service =
 
 150                 AtomicOperationUtils.getServiceObjectByNameAndVersion(DESIGNER, serviceName, serviceVersion);
 
 151             final Resource resourceToAdd =
 
 152                 AtomicOperationUtils.getResourceObjectByNameAndVersion(DESIGNER, resourceName, resourceVersion);
 
 153             final ComponentInstance componentInstance = AtomicOperationUtils
 
 154                 .addComponentInstanceToComponentContainer(resourceToAdd, service, DESIGNER, true,
 
 155                     String.valueOf(pointFromCanvasCenter.getX()), String.valueOf(pointFromCanvasCenter.getY()))
 
 158             LOGGER.debug("Created instance {} in the Service {}", componentInstance.getName(), serviceName);
 
 159             return componentInstance;
 
 160         } catch (final Exception e) {
 
 161             throw new CompositionCanvasRuntimeException("Could not create node through the API", e);
 
 165     public ComponentInstance createNodeOnResourceCanvas(final String serviceName, final String serviceVersion, final String resourceName,
 
 166                                                         final String resourceVersion) {
 
 167         final Point freePositionInCanvas = getFreePositionInCanvas(20);
 
 168         final Point pointFromCanvasCenter = calculateOffsetFromCenter(freePositionInCanvas);
 
 170             final Resource service = AtomicOperationUtils.getResourceObjectByNameAndVersion(DESIGNER, serviceName, serviceVersion);
 
 171             final Resource resourceToAdd = AtomicOperationUtils.getResourceObjectByNameAndVersion(DESIGNER, resourceName, resourceVersion);
 
 172             final ComponentInstance componentInstance =
 
 173                 AtomicOperationUtils.addComponentInstanceToComponentContainer(resourceToAdd, service, DESIGNER, true,
 
 174                     String.valueOf(pointFromCanvasCenter.getX()), String.valueOf(pointFromCanvasCenter.getY())).left().value();
 
 176             LOGGER.debug("Created instance {} in the Service {}", componentInstance.getName(), serviceName);
 
 177             return componentInstance;
 
 178         } catch (final Exception e) {
 
 179             throw new CompositionCanvasRuntimeException("Could not create node through the API", e);
 
 183     private Point getFreePositionInCanvas(int maxAttempts) {
 
 184         boolean isPositionFree;
 
 185         final int minSpace = 150;
 
 186         for (int attemptCount = 0; attemptCount < maxAttempts; attemptCount++) {
 
 187             final Point randomPositionInCanvas = getRandomPositionInCanvas();
 
 188             isPositionFree = canvasElementList.stream()
 
 189                 .noneMatch(canvasNodeElement -> Math.abs(canvasNodeElement.getPositionX() - randomPositionInCanvas.getX()) < minSpace
 
 190                     && Math.abs(canvasNodeElement.getPositionX() - randomPositionInCanvas.getY()) < minSpace);
 
 191             if (isPositionFree) {
 
 192                 return randomPositionInCanvas;
 
 195         throw new CompositionCanvasRuntimeException("Could not find a free Canvas position");
 
 198     private Point getRandomPositionInCanvas() {
 
 199         final Random random = new Random();
 
 200         int x = random.nextInt(canvasWidth);
 
 201         final int maxAllowedWidth = canvasWidth - getRightMarginWidth();
 
 202         final int minAllowedWidth = 30;
 
 203         if (x > maxAllowedWidth) {
 
 204             x = x - getRightMarginWidth();
 
 205         } else if (x < minAllowedWidth) {
 
 206             x = x + minAllowedWidth;
 
 208         int bottomMargin = 0;
 
 209         int heightTopMargin = 100;
 
 210         int y = random.nextInt(canvasHeight);
 
 211         int maxAllowedHeight = canvasHeight - bottomMargin;
 
 213         if (y > maxAllowedHeight) {
 
 214             y = y - bottomMargin;
 
 215         } else if (y < heightTopMargin) {
 
 216             y = y + heightTopMargin;
 
 218         LOGGER.debug("Generated random position in canvas [{},{}]", x, y);
 
 220         return new Point(x, y);
 
 223     private int getRightMarginWidth() {
 
 224         int canvasIconsOffset = 100;
 
 225         final Dimension sideBarSize = compositionDetailSideBarComponent.getSize();
 
 226         return sideBarSize.getWidth() + canvasIconsOffset;
 
 229     private Point calculateOffsetFromCenter(final Point point) {
 
 230         return calculateOffsetFromCenter(point.getX(), point.getY());
 
 233     private Point calculateOffsetFromCenter(final int xPosition, final int yPosition) {
 
 234         final int positionX = xPosition - canvasCenterX;
 
 235         final int positionY = yPosition - canvasCenterY;
 
 236         return new Point(positionX, positionY);
 
 239     public ImmutablePair<Integer, Integer> getElementPositionByName(final String elementName) {
 
 240         final String scriptJs = String.format(nodePositionJs, elementName);
 
 241         final Object position = ((JavascriptExecutor) webDriver).executeScript(scriptJs);
 
 242         final JsonObject positionAsJson = new JsonParser().parse(position.toString()).getAsJsonObject();
 
 243         int xElement = positionAsJson.get("x").getAsInt();
 
 244         int yElement = positionAsJson.get("y").getAsInt();
 
 245         return new ImmutablePair<>(xElement, yElement);
 
 248     public RelationshipWizardComponent createLink(final String fromNodeName, final String toNodeName) {
 
 249         final CanvasNodeElement fromCanvasElement = canvasElementList.stream()
 
 250             .filter(canvasNodeElement -> canvasNodeElement.getName().equals(fromNodeName)).findFirst()
 
 251             .orElseThrow(() -> new UiTestFlowRuntimeException(String.format("Could not find node '%s'", fromNodeName)));
 
 252         final CanvasNodeElement toCanvasElement = canvasElementList.stream()
 
 253             .filter(canvasNodeElement -> canvasNodeElement.getName().equals(toNodeName)).findFirst()
 
 254             .orElseThrow(() -> new UiTestFlowRuntimeException(String.format("Could not find node '%s'", toNodeName)));
 
 256         final Point greenPlusPosition = getElementGreenPlusPosition(fromCanvasElement.getName());
 
 257         final Point greenPlusPositionFromCenter = calculateOffsetFromCenter(greenPlusPosition);
 
 258         final Point toElementPositionFromCenter = calculateOffsetFromCenter(toCanvasElement.getPositionX(), toCanvasElement.getPositionY());
 
 259         new Actions(webDriver)
 
 260             .moveToElement(canvasWebElement, greenPlusPositionFromCenter.getX(), greenPlusPositionFromCenter.getY())
 
 261             .moveByOffset(3, 3).moveByOffset(-3, -3)
 
 262             .pause(Duration.ofSeconds(2))
 
 264             .pause(Duration.ofSeconds(1))
 
 265             .moveToElement(canvasWebElement, toElementPositionFromCenter.getX(), toElementPositionFromCenter.getY())
 
 266             .pause(Duration.ofSeconds(1))
 
 269         return new RelationshipWizardComponent(webDriver);
 
 272     public Point getElementGreenPlusPosition(final String elementName) {
 
 273         String scriptJS = "var cy = window.jQuery('.sdc-composition-graph-wrapper').cytoscape('get');\n"
 
 274             + "var cyZoom = cy.zoom();\n"
 
 275             + "var n = cy.nodes('[name=\"" + elementName + "\"]');\n"
 
 276             + "var nPos = n.renderedPosition();\n"
 
 277             + "var nData = n.data();\n"
 
 278             + "var nImgSize = nData.imgWidth;\n"
 
 279             + "var shiftSize = (nImgSize-18)*cyZoom/2;\n"
 
 280             + "return JSON.stringify({\n"
 
 281             + "\tx: nPos.x + shiftSize,\n"
 
 282             + "\ty: nPos.y - shiftSize\n"
 
 284         final String o = (String) ((JavascriptExecutor) webDriver).executeScript(scriptJS);
 
 285         final JsonObject node = new JsonParser().parse(o).getAsJsonObject();
 
 286         final int x = node.get("x").getAsInt();
 
 287         final int y = node.get("y").getAsInt();
 
 288         return new Point(x, y);
 
 292      * Enum that contains identifiers and xpath expressions to elements related to the enclosing page object.
 
 295     private enum XpathSelector {
 
 296         CANVAS_ELEMENT("canvas", "//*[@data-tests-id='%s']//canvas[1]");
 
 299         private final String id;
 
 300         private final String xpathFormat;
 
 302         public String getXpath() {
 
 303             return String.format(xpathFormat, id);