Add Composition Page and create resource page/flow 58/119558/2
authorandre.schmid <andre.schmid@est.tech>
Tue, 9 Mar 2021 18:29:48 +0000 (18:29 +0000)
committerChristophe Closset <christophe.closset@intl.att.com>
Mon, 22 Mar 2021 16:37:22 +0000 (16:37 +0000)
Add UI Tests Composition Page, and create resource page and flow

Change-Id: I004a2e8123df669e7318eca7feb58f8b1210dd65
Issue-ID: SDC-3523, SDC-3524
Signed-off-by: andre.schmid <andre.schmid@est.tech>
31 files changed:
catalog-ui/src/app/ng2/components/logic/substitution-filter/substitution-filter.component.html
catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.html
catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts
catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts
integration-tests/src/test/java/org/onap/sdc/backend/ci/tests/utils/general/AtomicOperationUtils.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/datatypes/CanvasNodeElement.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/datatypes/ResourceCreateData.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/exception/CompositionCanvasRuntimeException.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/EtsiNetworkServiceUiTests.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/execute/sanity/OnboardingFlowsUi.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/flow/CreateResourceFlow.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/flow/CreateResourceFromVspFlow.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/flow/CreateVspFlow.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/ResourceCreatePage.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/ResourceLeftSideMenu.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/ServiceComponentPage.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/TopNavComponent.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/VspRepositoryModalComponent.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionApiArtifactsTab.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionCanvasComponent.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionDeploymentArtifactsTab.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionDetailSideBarComponent.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionElementsComponent.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionInformationTab.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionInformationalArtifactsTab.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionInputsTab.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionPage.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionSubstitutionFilterTab.java [new file with mode: 0644]
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/home/HomePage.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/utilities/LoaderHelper.java
integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/utilities/MouseUtils.java [new file with mode: 0644]

index 01329b7..81fc5b4 100644 (file)
@@ -45,7 +45,7 @@
         <div class="w-sdc-designer-sidebar-section-substitution-filter-footer">
           <button
                   class="w-sdc-designer-sidebar-section-substitution-filter-footer-action add-rule-btn tlv-btn blue"
-                  data-tests-id="add-rule-button"
+                  data-tests-id="add-substitution-filter-button"
                   (click)="onAddSubstitutionFilter(PROPERTIES)"
                   [disabled]="readonly">
             {{'ADD_SUBSTITUTION_FILTER' | translate}}
index 5511dc0..61e2f5e 100644 (file)
@@ -12,7 +12,8 @@
             <sdc-tab *ngFor="let tab of tabs"
                      [titleIcon]="tab.titleIcon"
                      [active]="tab.isActive"
-                     [tooltipText]="tab.tooltipText">
+                     [tooltipText]="tab.tooltipText"
+                     [testId]="tab.testId">
 
                 <panel-tab [isActive]="tab.isActive"
                            [component]="selectedComponent"
index 6d96764..d5c0b60 100644 (file)
@@ -27,49 +27,113 @@ describe('composition-panel component', () => {
     let store: Store;
 
     const tabs = {
-            infoTab : {titleIcon: 'info-circle', component: InfoTabComponent, input: {}, isActive: true, tooltipText: 'Information'},
-            policyProperties: {
-                titleIcon: 'settings-o', component: GroupOrPolicyPropertiesTab, input: {type: 'policy'}, isActive: false, tooltipText: 'Properties'
-            },
-            policyTargets: {titleIcon: 'inputs-o', component: PolicyTargetsTabComponent, input: {}, isActive: false, tooltipText: 'Targets'},
-            groupMembers: {titleIcon: 'inputs-o', component: GroupMembersTabComponent, input: {}, isActive: false, tooltipText: 'Members'},
-            groupProperties: {
-                titleIcon: 'settings-o', component: GroupOrPolicyPropertiesTab, input: {type: 'group'}, isActive: false, tooltipText: 'Properties'
-            },
-            deploymentArtifacts: {
-                titleIcon: 'deployment-artifacts-o', component: ArtifactsTabComponent,
-                input: { type: ArtifactGroupType.DEPLOYMENT}, isActive: false, tooltipText: 'Deployment Artifacts'
-            },
-            apiArtifacts: {
-                titleIcon: 'api-o', component: ArtifactsTabComponent,
-                input: { type:  ArtifactGroupType.SERVICE_API}, isActive: false, tooltipText: 'API Artifacts'
-            },
-            infoArtifacts: {
-                titleIcon: 'info-square-o', component: ArtifactsTabComponent,
-                input: { type: ArtifactGroupType.INFORMATION}, isActive: false, tooltipText: 'Information Artifacts'
-            },
-            properties: {
-                titleIcon: 'settings-o', component: PropertiesTabComponent,
-                input: {title: 'Properties and Attributes'}, isActive: false, tooltipText: 'Properties'
-            },
-            reqAndCapabilities : {
-                titleIcon: 'req-capabilities-o', component: ReqAndCapabilitiesTabComponent, input: {},
-                isActive: false, tooltipText: 'Requirements and Capabilities'
-            },
-            substitutionFilter: {
-                titleIcon: 'composition-o', component: SubstitutionFilterTabComponent, input: {title: 'SUBSTITUTION FILTER'},
-                isActive: false, tooltipText: 'Substitution Filter'
-            },
-            inputs: {titleIcon: 'inputs-o', component: PropertiesTabComponent, input: {title: 'Inputs'}, isActive: false, tooltipText: 'Inputs'},
-            settings: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {}, isActive: false, tooltipText: 'Settings'},
-            interfaceOperations: {
-                titleIcon: 'composition-o',
-                component: InterfaceOperationsComponent,
-                input: {title: 'Interface Operations'},
-                isActive: false,
-                tooltipText: 'Interface Operations'
-            }
-        };
+        infoTab: {
+            titleIcon: 'info-circle',
+            component: InfoTabComponent,
+            input: {},
+            isActive: true,
+            tooltipText: 'Information',
+            testId: 'detail-tab-information'
+        },
+        policyProperties: {
+            titleIcon: 'settings-o',
+            component: GroupOrPolicyPropertiesTab,
+            input: {type: 'policy'},
+            isActive: false,
+            tooltipText: 'Properties',
+            testId: 'detail-tab-policy-properties'
+        },
+        policyTargets: {
+            titleIcon: 'inputs-o',
+            component: PolicyTargetsTabComponent,
+            input: {},
+            isActive: false,
+            tooltipText: 'Targets',
+            testId: 'detail-tab-policy-targets'
+        },
+        groupMembers: {
+            titleIcon: 'inputs-o',
+            component: GroupMembersTabComponent,
+            input: {},
+            isActive: false,
+            tooltipText: 'Members',
+            testId: 'detail-tab-group-members'
+        },
+        groupProperties: {
+            titleIcon: 'settings-o',
+            component: GroupOrPolicyPropertiesTab,
+            input: {type: 'group'},
+            isActive: false,
+            tooltipText: 'Properties',
+            testId: 'detail-tab-group-properties'
+        },
+        deploymentArtifacts: {
+            titleIcon: 'deployment-artifacts-o',
+            component: ArtifactsTabComponent,
+            input: {type: ArtifactGroupType.DEPLOYMENT},
+            isActive: false,
+            tooltipText: 'Deployment Artifacts',
+            testId: 'detail-tab-deployment-artifacts'
+        },
+        apiArtifacts: {
+            titleIcon: 'api-o',
+            component: ArtifactsTabComponent,
+            input: {type: ArtifactGroupType.SERVICE_API},
+            isActive: false,
+            tooltipText: 'API Artifacts',
+            testId: 'detail-tab-api-artifacts'
+        },
+        infoArtifacts: {
+            titleIcon: 'info-square-o',
+            component: ArtifactsTabComponent,
+            input: {type: ArtifactGroupType.INFORMATION},
+            isActive: false,
+            tooltipText: 'Information Artifacts',
+            testId: 'detail-tab-information-artifacts'
+        },
+        properties: {
+            titleIcon: 'settings-o', component: PropertiesTabComponent,
+            input: {title: 'Properties and Attributes'}, isActive: false, tooltipText: 'Properties',
+            testId: 'detail-tab-properties-attributes'
+        },
+        reqAndCapabilities: {
+            titleIcon: 'req-capabilities-o', component: ReqAndCapabilitiesTabComponent, input: {},
+            isActive: false, tooltipText: 'Requirements and Capabilities',
+            testId: 'detail-tab-requirements-capabilities'
+        },
+        substitutionFilter: {
+            titleIcon: 'composition-o',
+            component: SubstitutionFilterTabComponent,
+            input: {title: 'SUBSTITUTION FILTER'},
+            isActive: false,
+            tooltipText: 'Substitution Filter',
+            testId: 'detail-tab-substitution-filter'
+        },
+        inputs: {
+            titleIcon: 'inputs-o',
+            component: PropertiesTabComponent,
+            input: {title: 'Inputs'},
+            isActive: false,
+            tooltipText: 'Inputs',
+            testId: 'detail-tab-inputs'
+        },
+        settings: {
+            titleIcon: 'settings-o',
+            component: PropertiesTabComponent,
+            input: {},
+            isActive: false,
+            tooltipText: 'Settings',
+            testId: 'detail-tab-settings'
+        },
+        interfaceOperations: {
+            titleIcon: 'composition-o',
+            component: InterfaceOperationsComponent,
+            input: {title: 'Interface Operations'},
+            isActive: false,
+            tooltipText: 'Interface Operations',
+            testId: 'detail-tab-interface-operations'
+        }
+    };
 
     beforeEach(
         async(() => {
@@ -95,7 +159,6 @@ describe('composition-panel component', () => {
         const testInstance = new PolicyInstance();
 
         fixture.componentInstance.initTabs(testInstance);
-
         expect (fixture.componentInstance.tabs.length).toBe(3);
         expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab);
         expect (fixture.componentInstance.tabs[1]).toEqual(tabs.policyTargets);
index 2ef4e7c..6ed73b3 100644 (file)
@@ -42,22 +42,134 @@ import {SubstitutionFilterTabComponent} from "./panel-tabs/substitution-filter-t
 import {InterfaceOperationsComponent} from "../interface-operatons/interface-operations.component";
 
 const tabs = {
-    infoTab : {titleIcon: 'info-circle', component: InfoTabComponent, input: {}, isActive: true, tooltipText: 'Information'},
-    policyProperties: {titleIcon: 'settings-o', component: GroupOrPolicyPropertiesTab, input: {type: 'policy'}, isActive: false, tooltipText: 'Properties'},
-    policyTargets: {titleIcon: 'inputs-o', component: PolicyTargetsTabComponent, input: {}, isActive: false, tooltipText: 'Targets'},
-    groupMembers: {titleIcon: 'inputs-o', component: GroupMembersTabComponent, input: {}, isActive: false, tooltipText: 'Members'},
-    groupProperties: {titleIcon: 'settings-o', component: GroupOrPolicyPropertiesTab, input: {type: 'group'}, isActive: false, tooltipText: 'Properties'},
-    deploymentArtifacts: {titleIcon: 'deployment-artifacts-o', component: ArtifactsTabComponent, input: { type: ArtifactGroupType.DEPLOYMENT}, isActive: false, tooltipText: 'Deployment Artifacts'},
-    apiArtifacts: {titleIcon: 'api-o', component: ArtifactsTabComponent, input: { type:  ArtifactGroupType.SERVICE_API}, isActive: false, tooltipText: 'API Artifacts'},
-    infoArtifacts: {titleIcon: 'info-square-o', component: ArtifactsTabComponent, input: { type: ArtifactGroupType.INFORMATION}, isActive: false, tooltipText: 'Information Artifacts'},
-    properties: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {title: 'Properties and Attributes'}, isActive: false, tooltipText: 'Properties'},
-    reqAndCapabilities : { titleIcon: 'req-capabilities-o', component: ReqAndCapabilitiesTabComponent, input: {}, isActive: false, tooltipText: 'Requirements and Capabilities'},
-    inputs: {titleIcon: 'inputs-o', component: PropertiesTabComponent, input: {title: 'Inputs'}, isActive: false, tooltipText: 'Inputs'},
-    settings: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {}, isActive: false, tooltipText: 'Settings'},
-    consumption: {titleIcon: 'api-o', component: ServiceConsumptionTabComponent, input: {title: 'OPERATION CONSUMPTION'}, isActive: false, tooltipText: 'Service Consumption'},
-    dependencies: {titleIcon: 'archive', component: ServiceDependenciesTabComponent, input: {title: 'DIRECTIVES AND NODE FILTER'}, isActive: false, tooltipText: 'Service Dependencies'},
-    substitutionFilter: {titleIcon: 'composition-o', component: SubstitutionFilterTabComponent, input: {title: 'SUBSTITUTION FILTER'}, isActive: false, tooltipText: 'Substitution Filter'},
-    interfaceOperations: {titleIcon: 'composition-o', component: InterfaceOperationsComponent, input: {title: 'Interface Operations'}, isActive: false, tooltipText: 'Interface Operations'}
+    infoTab: {
+        titleIcon: 'info-circle',
+        component: InfoTabComponent,
+        input: {},
+        isActive: true,
+        tooltipText: 'Information',
+        testId: 'detail-tab-information'
+    },
+    policyProperties: {
+        titleIcon: 'settings-o',
+        component: GroupOrPolicyPropertiesTab,
+        input: {type: 'policy'},
+        isActive: false,
+        tooltipText: 'Properties',
+        testId: 'detail-tab-policy-properties'
+    },
+    policyTargets: {
+        titleIcon: 'inputs-o',
+        component: PolicyTargetsTabComponent,
+        input: {},
+        isActive: false,
+        tooltipText: 'Targets',
+        testId: 'detail-tab-policy-targets'
+    },
+    groupMembers: {
+        titleIcon: 'inputs-o',
+        component: GroupMembersTabComponent,
+        input: {},
+        isActive: false,
+        tooltipText: 'Members',
+        testId: 'detail-tab-group-members'
+    },
+    groupProperties: {
+        titleIcon: 'settings-o',
+        component: GroupOrPolicyPropertiesTab,
+        input: {type: 'group'},
+        isActive: false,
+        tooltipText: 'Properties',
+        testId: 'detail-tab-group-properties'
+    },
+    deploymentArtifacts: {
+        titleIcon: 'deployment-artifacts-o',
+        component: ArtifactsTabComponent,
+        input: {type: ArtifactGroupType.DEPLOYMENT},
+        isActive: false,
+        tooltipText: 'Deployment Artifacts',
+        testId: 'detail-tab-deployment-artifacts'
+    },
+    apiArtifacts: {
+        titleIcon: 'api-o',
+        component: ArtifactsTabComponent,
+        input: {type: ArtifactGroupType.SERVICE_API},
+        isActive: false,
+        tooltipText: 'API Artifacts',
+        testId: 'detail-tab-api-artifacts'
+    },
+    infoArtifacts: {
+        titleIcon: 'info-square-o',
+        component: ArtifactsTabComponent,
+        input: {type: ArtifactGroupType.INFORMATION},
+        isActive: false,
+        tooltipText: 'Information Artifacts',
+        testId: 'detail-tab-information-artifacts'
+    },
+    properties: {
+        titleIcon: 'settings-o',
+        component: PropertiesTabComponent,
+        input: {title: 'Properties and Attributes'},
+        isActive: false,
+        tooltipText: 'Properties',
+        testId: 'detail-tab-properties-attributes'
+    },
+    reqAndCapabilities: {
+        titleIcon: 'req-capabilities-o',
+        component: ReqAndCapabilitiesTabComponent,
+        input: {},
+        isActive: false,
+        tooltipText: 'Requirements and Capabilities',
+        testId: 'detail-tab-requirements-capabilities'
+    },
+    inputs: {
+        titleIcon: 'inputs-o',
+        component: PropertiesTabComponent,
+        input: {title: 'Inputs'},
+        isActive: false,
+        tooltipText: 'Inputs',
+        testId: 'detail-tab-inputs'
+    },
+    settings: {
+        titleIcon: 'settings-o',
+        component: PropertiesTabComponent,
+        input: {},
+        isActive: false,
+        tooltipText: 'Settings',
+        testId: 'detail-tab-settings'
+    },
+    consumption: {
+        titleIcon: 'api-o',
+        component: ServiceConsumptionTabComponent,
+        input: {title: 'OPERATION CONSUMPTION'},
+        isActive: false,
+        tooltipText: 'Service Consumption',
+        testId: 'detail-tab-operation-consumption'
+    },
+    dependencies: {
+        titleIcon: 'archive',
+        component: ServiceDependenciesTabComponent,
+        input: {title: 'DIRECTIVES AND NODE FILTER'},
+        isActive: false,
+        tooltipText: 'Service Dependencies',
+        testId: 'detail-tab-directives-node-filter'
+    },
+    substitutionFilter: {
+        titleIcon: 'composition-o',
+        component: SubstitutionFilterTabComponent,
+        input: {title: 'SUBSTITUTION FILTER'},
+        isActive: false,
+        tooltipText: 'Substitution Filter',
+        testId: 'detail-tab-substitution-filter'
+    },
+    interfaceOperations: {
+        titleIcon: 'composition-o',
+        component: InterfaceOperationsComponent,
+        input: {title: 'Interface Operations'},
+        isActive: false,
+        tooltipText: 'Interface Operations',
+        testId: 'detail-tab-interface-operations'
+    }
 };
 
 @Component({
index c69d70a..9c73275 100644 (file)
@@ -472,6 +472,9 @@ public final class AtomicOperationUtils {
                        ComponentInstanceReqDetails componentInstanceDetails = ElementFactory.getComponentInstance(compInstParent);
                        componentInstanceDetails.setPosX(positionX);
                        componentInstanceDetails.setPosY(positionY);
+                       if (componentInstanceDetails.getOriginType() == null){
+                               componentInstanceDetails.setOriginType(((Resource) compInstParent).getResourceType().toString());
+                       }
                        RestResponse createComponentInstance = ComponentInstanceRestUtils.createComponentInstance(componentInstanceDetails, defaultUser, compContainer);
 
                        if (validateState) {
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/datatypes/CanvasNodeElement.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/datatypes/CanvasNodeElement.java
new file mode 100644 (file)
index 0000000..044044f
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.datatypes;
+
+import java.util.Objects;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.component.workspace.CompositionCanvasComponent;
+
+/**
+ * Represents a node in the {@link CompositionCanvasComponent}
+ */
+@Getter
+@AllArgsConstructor
+public final class CanvasNodeElement {
+
+    private final String name;
+    private final int positionX;
+    private final int positionY;
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final CanvasNodeElement that = (CanvasNodeElement) o;
+        return name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name);
+    }
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/datatypes/ResourceCreateData.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/datatypes/ResourceCreateData.java
new file mode 100644 (file)
index 0000000..ea63ead
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.datatypes;
+
+import java.util.List;
+import java.util.UUID;
+import lombok.Data;
+
+/**
+ * Represents the necessary data to create a resource (VF, VFC or similar)
+ */
+@Data
+public class ResourceCreateData {
+
+    private String name;
+    private String category;
+    private List<String> tagList;
+    private String description;
+    private String contactId;
+    private String vendorName;
+    private String vendorRelease;
+    private String vendorModelNumber;
+
+    public void setRandomName(final String prefix) {
+        final String randomPart = UUID.randomUUID().toString().split("-")[0];
+        this.name = String.format("%s%s", prefix == null ? "" : prefix, randomPart);
+    }
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/exception/CompositionCanvasRuntimeException.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/exception/CompositionCanvasRuntimeException.java
new file mode 100644 (file)
index 0000000..6f3b839
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.exception;
+
+public class CompositionCanvasRuntimeException extends RuntimeException {
+
+    public CompositionCanvasRuntimeException(final String s) {
+        super(s);
+    }
+
+    public CompositionCanvasRuntimeException(final String s, final Throwable throwable) {
+        super(s, throwable);
+    }
+
+}
index 0854bec..feb5641 100644 (file)
@@ -27,7 +27,6 @@ import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.Assert.fail;
 
-import com.aventstack.extentreports.Status;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -35,7 +34,6 @@ import java.util.Optional;
 import org.apache.commons.io.FilenameUtils;
 import org.junit.jupiter.api.Assertions;
 import org.onap.sdc.backend.ci.tests.datatypes.enums.ServiceCategoriesEnum;
-import org.onap.sdc.backend.ci.tests.datatypes.enums.UserRoleEnum;
 import org.onap.sdc.frontend.ci.tests.datatypes.ServiceCreateData;
 import org.onap.sdc.frontend.ci.tests.exception.UnzipException;
 import org.onap.sdc.frontend.ci.tests.execute.setup.DriverFactory;
@@ -231,3 +229,4 @@ public class EtsiNetworkServiceUiTests extends SetupCDTest {
     }
 
 }
+
index f2e6036..cf562c7 100644 (file)
@@ -52,11 +52,12 @@ import org.onap.sdc.frontend.ci.tests.dataProvider.OnbordingDataProviders;
 import org.onap.sdc.frontend.ci.tests.datatypes.CanvasElement;
 import org.onap.sdc.frontend.ci.tests.datatypes.CanvasManager;
 import org.onap.sdc.frontend.ci.tests.datatypes.DataTestIdEnum;
+import org.onap.sdc.frontend.ci.tests.datatypes.ResourceCreateData;
 import org.onap.sdc.frontend.ci.tests.execute.setup.DriverFactory;
 import org.onap.sdc.frontend.ci.tests.execute.setup.ExtentTestActions;
 import org.onap.sdc.frontend.ci.tests.execute.setup.SetupCDTest;
 import org.onap.sdc.frontend.ci.tests.flow.CheckSoftwareVersionPropertyFlow;
-import org.onap.sdc.frontend.ci.tests.flow.CreateResourceFlow;
+import org.onap.sdc.frontend.ci.tests.flow.CreateResourceFromVspFlow;
 import org.onap.sdc.frontend.ci.tests.flow.CreateVspFlow;
 import org.onap.sdc.frontend.ci.tests.flow.ImportVspFlow;
 import org.onap.sdc.frontend.ci.tests.flow.exception.UiTestFlowRuntimeException;
@@ -396,7 +397,7 @@ public class OnboardingFlowsUi extends SetupCDTest {
         final ImportVspFlow importVspFlow = new ImportVspFlow(webDriver, resourceName);
         final ResourceCreatePage resourceCreatePage = importVspFlow.run()
             .orElseThrow(() -> new UiTestFlowRuntimeException("Missing expected return ResourceCreatePage"));
-        final CreateResourceFlow createResourceFlow = new CreateResourceFlow(webDriver, resourceName);
+        final CreateResourceFromVspFlow createResourceFlow = new CreateResourceFromVspFlow(webDriver, resourceName);
         createResourceFlow.run(resourceCreatePage);
 
         final CheckSoftwareVersionPropertyFlow checkSoftwareVersionPropertyFlow =
index 0965057..bdd2b92 100644 (file)
 package org.onap.sdc.frontend.ci.tests.flow;
 
 import com.aventstack.extentreports.Status;
-import org.onap.sdc.frontend.ci.tests.pages.ResourceCreatePage;
+import java.util.Optional;
+import org.onap.sdc.frontend.ci.tests.datatypes.ResourceCreateData;
 import org.onap.sdc.frontend.ci.tests.execute.setup.ExtentTestActions;
 import org.onap.sdc.frontend.ci.tests.pages.PageObject;
+import org.onap.sdc.frontend.ci.tests.pages.ResourceCreatePage;
+import org.onap.sdc.frontend.ci.tests.pages.home.HomePage;
 import org.openqa.selenium.WebDriver;
 
-import java.util.Optional;
-
 /**
  * UI Flow for Resource creation
  */
 public class CreateResourceFlow extends AbstractUiTestFlow {
 
-    private final String resourceName;
+    private final ResourceCreateData resourceCreateData;
     private ResourceCreatePage resourceCreatePage;
 
-    public CreateResourceFlow(final WebDriver webDriver, final String resourceName) {
+    public CreateResourceFlow(final WebDriver webDriver, final ResourceCreateData resourceCreateData) {
         super(webDriver);
-        this.resourceName = resourceName;
+        this.resourceCreateData = resourceCreateData;
     }
 
     @Override
     public Optional<ResourceCreatePage> run(final PageObject... pageObjects) {
-        resourceCreatePage = findParameter(pageObjects, ResourceCreatePage.class);
-        extendTest.log(Status.INFO, String.format("Creating the Resource '%s'", resourceName));
-        resourceCreatePage.createResource();
+        extendTest.log(Status.INFO, String.format("Creating the Resource '%s' from home page", resourceCreateData.getName()));
+        final HomePage homePage = findParameter(pageObjects, HomePage.class);
+        homePage.isLoaded();
+        resourceCreatePage = homePage.clickOnAddVf();
+        resourceCreatePage.fillForm(resourceCreateData);
+        resourceCreatePage.clickOnCreate();
         ExtentTestActions.takeScreenshot(Status.INFO, "resource-created",
-            String.format("Resource '%s' was created", resourceName));
-        return Optional.ofNullable(resourceCreatePage);
+            String.format("Resource '%s' was created", resourceCreateData.getName()));
+        return Optional.ofNullable(this.resourceCreatePage);
     }
 
     @Override
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/flow/CreateResourceFromVspFlow.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/flow/CreateResourceFromVspFlow.java
new file mode 100644 (file)
index 0000000..b4e8381
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.flow;
+
+import com.aventstack.extentreports.Status;
+import org.onap.sdc.frontend.ci.tests.datatypes.ResourceCreateData;
+import org.onap.sdc.frontend.ci.tests.pages.ResourceCreatePage;
+import org.onap.sdc.frontend.ci.tests.execute.setup.ExtentTestActions;
+import org.onap.sdc.frontend.ci.tests.pages.PageObject;
+import org.openqa.selenium.WebDriver;
+
+import java.util.Optional;
+
+/**
+ * UI Flow for Resource creation
+ */
+public class CreateResourceFromVspFlow extends AbstractUiTestFlow {
+
+    private final String resourceName;
+    private ResourceCreatePage resourceCreatePage;
+
+    public CreateResourceFromVspFlow(final WebDriver webDriver, final String resourceName) {
+        super(webDriver);
+        this.resourceName = resourceName;
+    }
+
+    @Override
+    public Optional<ResourceCreatePage> run(final PageObject... pageObjects) {
+        resourceCreatePage = findParameter(pageObjects, ResourceCreatePage.class);
+        extendTest.log(Status.INFO, String.format("Creating the Resource '%s'", resourceName));
+        resourceCreatePage.clickOnCreate();
+        ExtentTestActions.takeScreenshot(Status.INFO, "resource-created",
+            String.format("Resource '%s' was created", resourceName));
+        return Optional.ofNullable(resourceCreatePage);
+    }
+
+    @Override
+    public Optional<ResourceCreatePage> getLandedPage() {
+        return Optional.ofNullable(resourceCreatePage);
+    }
+
+}
index 3d3d442..1da45b5 100644 (file)
@@ -140,9 +140,8 @@ public class CreateVspFlow extends AbstractUiTestFlow {
     private void goToHomePage(final TopNavComponent topNavComponent) {
         extendTest.log(Status.INFO, "Accessing the Home page to import the created VSP");
         topNavComponent.isLoaded();
-        topNavComponent.clickOnHome();
+        homePage = topNavComponent.clickOnHome();
         GeneralUIUtils.ultimateWait();
-        homePage = new HomePage(webDriver, topNavComponent);
         homePage.isLoaded();
         ExtentTestActions.takeScreenshot(Status.INFO, "home-is-loaded", "The Home page is loaded.");
     }
index bb4ccb9..025c42a 100644 (file)
@@ -26,51 +26,102 @@ import static org.hamcrest.core.Is.is;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import org.onap.sdc.frontend.ci.tests.datatypes.LifeCycleStateEnum;
+import org.onap.sdc.frontend.ci.tests.datatypes.ResourceCreateData;
 import org.onap.sdc.frontend.ci.tests.utilities.LoaderHelper;
 import org.onap.sdc.frontend.ci.tests.utilities.NotificationComponent;
 import org.onap.sdc.frontend.ci.tests.utilities.NotificationComponent.NotificationType;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.ui.ExpectedConditions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.openqa.selenium.support.ui.Select;
 
 /**
  * Handles the Resource Create Page UI actions
  */
 public class ResourceCreatePage extends AbstractPageObject {
 
-    private static final Logger LOGGER = LoggerFactory.getLogger(ResourceCreatePage.class);
     private final LoaderHelper loaderHelper;
     private final NotificationComponent notificationComponent;
-    private WebElement createBtn;
+    private final ResourceWorkspaceTopBarComponent topBarComponent;
 
-    public ResourceCreatePage(final WebDriver webDriver, final LoaderHelper loaderHelper,
-                              final NotificationComponent notificationComponent) {
+    public ResourceCreatePage(final WebDriver webDriver) {
         super(webDriver);
-        this.loaderHelper = loaderHelper;
-        this.notificationComponent = notificationComponent;
+        loaderHelper = new LoaderHelper(webDriver);
+        notificationComponent = new NotificationComponent(webDriver);
+        topBarComponent = new ResourceWorkspaceTopBarComponent(webDriver);
         timeoutInSeconds = 5;
     }
 
     @Override
     public void isLoaded() {
-        LOGGER.debug("Waiting for element visibility with xpath '{}'", XpathSelector.FORM_LIFE_CYCLE_STATE.getXpath());
-        final WebElement lifeCycleState = waitForElementVisibility(XpathSelector.FORM_LIFE_CYCLE_STATE.getXpath());
+        topBarComponent.isLoaded();
+        final String lifeCycleState = topBarComponent.getLifecycleState();
         assertThat("Life cycle state should be as expected",
-            lifeCycleState.getText(), is(equalToIgnoringCase(LifeCycleStateEnum.IN_DESIGN.getValue())));
-        createBtn = getWait()
-            .until(ExpectedConditions.elementToBeClickable(By.xpath(XpathSelector.CREATE_BTN.getXpath())));
+            lifeCycleState, is(equalToIgnoringCase(LifeCycleStateEnum.IN_DESIGN.getValue())));
     }
 
     /**
      * Creates the resource and wait for success notification.
      */
-    public void createResource() {
-        createBtn.click();
-        loaderHelper.waitForLoader(60);
-        notificationComponent.waitForNotification(NotificationType.SUCCESS, 60);
+    public void clickOnCreate() {
+        topBarComponent.clickOnCreate();
+        loaderHelper.waitForLoader(20);
+        notificationComponent.waitForNotification(NotificationType.SUCCESS, 20);
+    }
+
+    public void fillForm(final ResourceCreateData resourceCreateData) {
+        fillName(resourceCreateData.getName());
+        setCategory(resourceCreateData.getCategory());
+        fillDescription(resourceCreateData.getDescription());
+        fillContactId(resourceCreateData.getContactId());
+        fillVendorName(resourceCreateData.getVendorName());
+        fillVendorRelease(resourceCreateData.getVendorRelease());
+        fillVendorModelNumber(resourceCreateData.getVendorModelNumber());
+    }
+
+    public void fillName(final String name) {
+        setInputField(By.xpath(XpathSelector.NAME_INPUT.getXpath()), name);
+    }
+
+    public void setCategory(final String category) {
+        setSelectField(By.xpath(XpathSelector.CATEGORY_SELECT.getXpath()), category);
+    }
+
+    public void fillDescription(final String description) {
+        setTextAreaField(By.xpath(XpathSelector.DESCRIPTION_TEXT_AREA.getXpath()), description);
+    }
+
+    public void fillContactId(final String contactId) {
+        setInputField(By.xpath(XpathSelector.CONTACT_ID_INPUT.getXpath()), contactId);
+    }
+
+    public void fillVendorName(final String vendorName) {
+        setInputField(By.xpath(XpathSelector.VENDOR_NAME_INPUT.getXpath()), vendorName);
+    }
+
+    public void fillVendorRelease(final String vendorRelease) {
+        setInputField(By.xpath(XpathSelector.VENDOR_RELEASE_INPUT.getXpath()), vendorRelease);
+    }
+
+    public void fillVendorModelNumber(final String vendorModelNumber) {
+        setInputField(By.xpath(XpathSelector.VENDOR_MODEL_NUMBER_INPUT.getXpath()), vendorModelNumber);
+    }
+
+    private void setSelectField(final By locator, final String value) {
+        if (value == null) {
+            return;
+        }
+        new Select(findElement(locator)).selectByVisibleText(value);
+    }
+
+    private void setInputField(final By locator, final String value) {
+        if (value == null) {
+            return;
+        }
+        findElement(locator).sendKeys(value);
+    }
+
+    private void setTextAreaField(final By locator, final String value) {
+        setInputField(locator, value);
     }
 
     /**
@@ -78,8 +129,13 @@ public class ResourceCreatePage extends AbstractPageObject {
      */
     @AllArgsConstructor
     private enum XpathSelector {
-        CREATE_BTN("create/save", "//button[@data-tests-id='%s']"),
-        FORM_LIFE_CYCLE_STATE("formlifecyclestate", "//span[@data-tests-id='%s']");
+        NAME_INPUT("name", "//input[@data-tests-id='%s']"),
+        CATEGORY_SELECT("selectGeneralCategory", "//select[@data-tests-id='%s']"),
+        DESCRIPTION_TEXT_AREA("description", "//textarea[@data-tests-id='%s']"),
+        CONTACT_ID_INPUT("contactId", "//input[@data-tests-id='%s']"),
+        VENDOR_NAME_INPUT("vendorName", "//input[@data-tests-id='%s']"),
+        VENDOR_RELEASE_INPUT("vendorRelease", "//input[@data-tests-id='%s']"),
+        VENDOR_MODEL_NUMBER_INPUT("resourceVendorModelNumber", "//input[@data-tests-id='%s']");
 
         @Getter
         private final String id;
index b58a3ad..f94b303 100644 (file)
@@ -21,6 +21,7 @@ package org.onap.sdc.frontend.ci.tests.pages;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.component.workspace.CompositionPage;
 import org.onap.sdc.frontend.ci.tests.pages.component.workspace.ToscaArtifactsPage;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
@@ -84,6 +85,11 @@ public class ResourceLeftSideMenu extends AbstractPageObject {
         return new ServiceCreatePage(webDriver);
     }
 
+    public CompositionPage clickOnCompositionMenuItem() {
+        wrappingElement.findElement(By.xpath(XpathSelector.COMPOSITION_MENU.getXpath())).click();
+        return new CompositionPage(webDriver);
+    }
+
     /**
      * Enum that contains identifiers and xpath expressions to elements related to the enclosing page object.
      */
@@ -92,6 +98,7 @@ public class ResourceLeftSideMenu extends AbstractPageObject {
         MAIN_DIV("w-sdc-left-sidebar", "//div[@class='%s']"),
         PROPERTIES_ASSIGNMENT_MENU("Properties AssignmentLeftSideMenu", "//*[@data-tests-id='%s']"),
         GENERAL_MENU("GeneralLeftSideMenu", "//*[@data-tests-id='%s']"),
+        COMPOSITION_MENU("CompositionLeftSideMenu", "//*[@data-tests-id='%s']"),
         TOSCA_ARTIFACTS_MENU("TOSCA ArtifactsLeftSideMenu", "//*[@data-tests-id='%s']");
 
         @Getter
index 254a0ec..a33bdc3 100644 (file)
@@ -19,6 +19,7 @@
 
 package org.onap.sdc.frontend.ci.tests.pages;
 
+import org.onap.sdc.frontend.ci.tests.pages.component.workspace.CompositionPage;
 import org.onap.sdc.frontend.ci.tests.pages.component.workspace.ToscaArtifactsPage;
 import org.openqa.selenium.WebDriver;
 
@@ -28,6 +29,13 @@ public class ServiceComponentPage extends AbstractPageObject {
     private final ResourceLeftSideMenu resourceLeftSideMenu;
     private final ResourceWorkspaceTopBarComponent workspaceTopBarComponent;
 
+    public ServiceComponentPage(final WebDriver webDriver) {
+        super(webDriver);
+        this.topNavComponent = new TopNavComponent(webDriver);
+        this.resourceLeftSideMenu = new ResourceLeftSideMenu(webDriver);
+        this.workspaceTopBarComponent = new ResourceWorkspaceTopBarComponent(webDriver);
+    }
+
     public ServiceComponentPage(final WebDriver webDriver, final TopNavComponent topNavComponent,
                                 final ResourceLeftSideMenu resourceLeftSideMenu,
                                 final ResourceWorkspaceTopBarComponent workspaceTopBarComponent) {
@@ -52,4 +60,8 @@ public class ServiceComponentPage extends AbstractPageObject {
         return resourceLeftSideMenu.clickOnToscaArtifactsMenuItem();
     }
 
+    public CompositionPage goToComposition() {
+        return resourceLeftSideMenu.clickOnCompositionMenuItem();
+    }
+
 }
index 56b584a..33deabb 100644 (file)
@@ -23,6 +23,7 @@ import java.util.List;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import org.onap.sdc.frontend.ci.tests.execute.setup.DriverFactory;
+import org.onap.sdc.frontend.ci.tests.pages.home.HomePage;
 import org.onap.sdc.frontend.ci.tests.utilities.GeneralUIUtils;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
@@ -65,24 +66,39 @@ public class TopNavComponent extends AbstractPageObject {
     /**
      * Clicks on home link inside the first breadcrumb arrow.
      */
-    public void clickOnHome() {
+    public HomePage clickOnHome() {
         hoverToBreadcrumbArrow(0);
         final By homeButtonLocator = By.xpath(XpathSelector.SUB_MENU_BUTTON_HOME.getXpath());
-        getWait().until(ExpectedConditions.visibilityOfElementLocated(homeButtonLocator));
-        getWait().until(ExpectedConditions.elementToBeClickable(homeButtonLocator)).click();
-        getWait()
-            .until(ExpectedConditions.visibilityOfElementLocated(By.xpath(XpathSelector.REPOSITORY_ICON.getXpath())));
+        waitForElementVisibility(homeButtonLocator);
+        waitToBeClickable(homeButtonLocator).click();
+        return new HomePage(webDriver, this);
     }
 
     public boolean isHomeSelected() {
         final By homeLinkLocator = By.xpath(XpathSelector.MAIN_MENU_LINK_HOME.getXpath());
-        getWait().until(ExpectedConditions.visibilityOfElementLocated(homeLinkLocator));
-        final WebElement homeLinkElement = findElement(homeLinkLocator);
+        final WebElement homeLinkElement = waitForElementVisibility(homeLinkLocator);
         final WebElement homeLinkParentElement = homeLinkElement.findElement(By.xpath("./.."));
         final String aClass = homeLinkParentElement.getAttribute("class");
         return "selected".equals(aClass);
     }
 
+    /**
+     * Click on one of the items of the top nav breadcrumb based on the given position.
+     * The first item in the breadcrumb is position 0.
+     *
+     * @param position the position of the breadcrumb item
+     */
+    public void clickOnBreadCrumb(final int position) {
+        if (position < 0) {
+            throw new IllegalStateException("The position cannot be less that zero");
+        }
+        waitForElementVisibility(By.xpath(String.format("//*[@data-tests-id='breadcrumbs-button-%s']", position))).click();
+    }
+
+    public void waitRepositoryToBeClickable() {
+        waitToBeClickable(XpathSelector.REPOSITORY_ICON.getXpath());
+    }
+
     /**
      * Clicks on the VSP repository icon.
      *
index 0cf7dbd..4cbe805 100644 (file)
@@ -28,8 +28,6 @@ import java.util.List;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import org.onap.sdc.frontend.ci.tests.utilities.GeneralUIUtils;
-import org.onap.sdc.frontend.ci.tests.utilities.LoaderHelper;
-import org.onap.sdc.frontend.ci.tests.utilities.NotificationComponent;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
 import org.openqa.selenium.WebElement;
@@ -70,7 +68,7 @@ public class VspRepositoryModalComponent extends AbstractPageObject {
             findSubElements(wrappingElement, By.className(XpathSelector.RESULTS_CONTAINER_DIV.getId()));
         vspResultList.get(listPosition).click();
         GeneralUIUtils.clickOnElementByTestId(XpathSelector.IMPORT_VSP_BTN.getId());
-        return new ResourceCreatePage(webDriver, new LoaderHelper(), new NotificationComponent(webDriver));
+        return new ResourceCreatePage(webDriver);
     }
 
     /**
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionApiArtifactsTab.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionApiArtifactsTab.java
new file mode 100644 (file)
index 0000000..df611a8
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * Represents the composition page, details panel, API Artifacts tab
+ */
+public class CompositionApiArtifactsTab extends AbstractPageObject {
+
+    public CompositionApiArtifactsTab(final WebDriver webDriver) {
+        super(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        waitForElementVisibility(By.xpath(XpathSelector.API_ARTIFACTS_TAB.getXpath()));
+    }
+
+    @AllArgsConstructor
+    @Getter
+    private enum XpathSelector {
+        API_ARTIFACTS_TAB("//artifacts-tab[.//header[contains(text(), 'API Artifacts')]]");
+
+        private final String xpath;
+
+    }
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionCanvasComponent.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionCanvasComponent.java
new file mode 100644 (file)
index 0000000..6ecea47
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import com.aventstack.extentreports.Status;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Set;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.onap.sdc.backend.ci.tests.datatypes.enums.UserRoleEnum;
+import org.onap.sdc.backend.ci.tests.utils.general.AtomicOperationUtils;
+import org.onap.sdc.frontend.ci.tests.datatypes.CanvasNodeElement;
+import org.onap.sdc.frontend.ci.tests.exception.CompositionCanvasRuntimeException;
+import org.onap.sdc.frontend.ci.tests.execute.setup.ExtentTestActions;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.openecomp.sdc.be.model.ComponentInstance;
+import org.openecomp.sdc.be.model.Resource;
+import org.openecomp.sdc.be.model.Service;
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.Point;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CompositionCanvasComponent extends AbstractPageObject {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CompositionCanvasComponent.class);
+    private static final String nodePositionJs = "var cy = window.jQuery('.sdc-composition-graph-wrapper').cytoscape('get');%n"
+        + "var n = cy.nodes('[name=\"%s\"]');%n"
+        + "var nPos = n.renderedPosition();%n"
+        + "return JSON.stringify({%n"
+        + "    x: nPos.x,%n"
+        + "    y: nPos.y%n"
+        + "})";
+
+    private static final String getNodesJs = "var cy = window.jQuery('.sdc-composition-graph-wrapper').cytoscape('get');\n"
+        + "var nodes = [];"
+        + "cy.nodes().forEach((node) => {nodes.push(JSON.stringify({name: node.data('name'), position: node.renderedPosition()}))});\n"
+        + "return nodes;";
+
+    private final CompositionElementsComponent compositionElementsComponent;
+    private final CompositionDetailSideBarComponent compositionDetailSideBarComponent;
+
+    private WebElement canvasWebElement;
+    private Set<CanvasNodeElement> canvasElementList;
+    private int canvasCenterX;
+    private int canvasCenterY;
+    private int canvasWidth;
+    private int canvasHeight;
+
+    public CompositionCanvasComponent(final WebDriver webDriver) {
+        super(webDriver);
+        compositionElementsComponent = new CompositionElementsComponent(webDriver);
+        compositionDetailSideBarComponent = new CompositionDetailSideBarComponent(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        //waiting the canvas data to be load and animation finishes.
+        new Actions(webDriver).pause(Duration.ofSeconds(2)).perform();
+        canvasWebElement = waitToBeClickable(XpathSelector.CANVAS_ELEMENT.getXpath());
+        compositionElementsComponent.isLoaded();
+        compositionDetailSideBarComponent.isLoaded();
+        loadCanvas();
+        loadElements();
+    }
+
+    private void loadCanvas() {
+        canvasWidth = canvasWebElement.getSize().getWidth();
+        canvasHeight = canvasWebElement.getSize().getHeight();
+        canvasCenterX = canvasWidth / 2;
+        canvasCenterY = canvasHeight / 2;
+        LOGGER.debug("Canvas with size [{}, {}] and center [{}, {}]", canvasWidth, canvasHeight, canvasCenterX, canvasCenterY);
+        final String scriptJS = "var cy = window.jQuery('.sdc-composition-graph-wrapper').cytoscape('get');\n"
+            + "return JSON.stringify({width: cy.width(), height: cy.height()});";
+        final Object sizeObj = ((JavascriptExecutor) webDriver).executeScript(scriptJS);
+        final JsonObject size = new JsonParser().parse(sizeObj.toString()).getAsJsonObject();
+
+        LOGGER.debug("Canvas with size [{}, {}]", size.get("width"), size.get("height"));
+    }
+
+    private void loadElements() {
+        canvasElementList = new HashSet<>();
+        final Object nodeListObj = ((JavascriptExecutor) webDriver).executeScript(getNodesJs);
+        if (!(nodeListObj instanceof ArrayList)) {
+            return;
+        }
+        final ArrayList<String> nodeList = (ArrayList<String>) nodeListObj;
+        if (nodeList.isEmpty()) {
+            return;
+        }
+        nodeList.forEach(nodeString -> {
+            final JsonObject node = new JsonParser().parse(nodeString).getAsJsonObject();
+            final JsonObject position = node.get("position").getAsJsonObject();
+            final CanvasNodeElement canvasElement =
+                new CanvasNodeElement(node.get("name").getAsString(), position.get("x").getAsInt(), position.get("y").getAsInt());
+            canvasElementList.add(canvasElement);
+        });
+    }
+
+    public void selectNode(final String elementName) {
+        final Optional<CanvasNodeElement> canvasElementOptional = canvasElementList.stream()
+            .filter(canvasNodeElement -> canvasNodeElement.getName().equals(elementName))
+            .findFirst();
+        if (canvasElementOptional.isEmpty()) {
+            throw new CompositionCanvasRuntimeException(String.format("Given element '%s' does not exist on the element list", elementName));
+        }
+        final CanvasNodeElement canvasNodeElement = canvasElementOptional.get();
+        final Point positionFromCenter = calculateOffsetFromCenter(canvasNodeElement.getPositionX(),
+            canvasNodeElement.getPositionY());
+        final Actions actions = new Actions(webDriver);
+        int offsetFromElementCenter = 10;
+        actions.moveToElement(canvasWebElement, positionFromCenter.getX() - offsetFromElementCenter,
+            positionFromCenter.getY() + offsetFromElementCenter)
+            .pause(Duration.ofSeconds(1))
+            .click()
+            .perform();
+        ExtentTestActions.takeScreenshot(Status.INFO, "canvas-node-selected", String.format("'%s' was selected", elementName));
+    }
+
+    public ComponentInstance createNodeOnServiceCanvas(final String serviceName, final String serviceVersion, final String resourceName,
+                                                       final String resourceVersion) {
+        final Point freePositionInCanvas = getFreePositionInCanvas(20);
+        final Point pointFromCanvasCenter = calculateOffsetFromCenter(freePositionInCanvas);
+        try {
+            final Service service =
+                AtomicOperationUtils.getServiceObjectByNameAndVersion(UserRoleEnum.DESIGNER, serviceName, serviceVersion);
+            final Resource resourceToAdd =
+                AtomicOperationUtils.getResourceObjectByNameAndVersion(UserRoleEnum.DESIGNER, resourceName, resourceVersion);
+            final ComponentInstance componentInstance = AtomicOperationUtils
+                .addComponentInstanceToComponentContainer(resourceToAdd, service, UserRoleEnum.DESIGNER, true,
+                    String.valueOf(pointFromCanvasCenter.getX()), String.valueOf(pointFromCanvasCenter.getY()))
+                .left().value();
+
+            LOGGER.debug("Created instance {} in the Service {}", componentInstance.getName(), serviceName);
+            return componentInstance;
+        } catch (final Exception e) {
+            throw new CompositionCanvasRuntimeException("Could not create node through the API", e);
+        }
+    }
+
+    private Point getFreePositionInCanvas(int maxAttempts) {
+        boolean isPositionFree;
+        final int minSpace = 150;
+        for (int attemptCount = 0; attemptCount < maxAttempts; attemptCount++) {
+            final Point randomPositionInCanvas = getRandomPositionInCanvas();
+            isPositionFree = canvasElementList.stream()
+                .noneMatch(canvasNodeElement -> Math.abs(canvasNodeElement.getPositionX() - randomPositionInCanvas.getX()) < minSpace
+                    && Math.abs(canvasNodeElement.getPositionX() - randomPositionInCanvas.getY()) < minSpace);
+            if (isPositionFree) {
+                return randomPositionInCanvas;
+            }
+        }
+        throw new CompositionCanvasRuntimeException("Could not find a free Canvas position");
+    }
+
+    private Point getRandomPositionInCanvas() {
+        final Random random = new Random();
+        int x = random.nextInt(canvasWidth);
+        final int maxAllowedWidth = canvasWidth - getRightMarginWidth();
+        final int minAllowedWidth = 30;
+        if (x > maxAllowedWidth) {
+            x = x - getRightMarginWidth();
+        } else if (x < minAllowedWidth) {
+            x = x + minAllowedWidth;
+        }
+        int bottomMargin = 0;
+        int heightTopMargin = 100;
+        int y = random.nextInt(canvasHeight);
+        int maxAllowedHeight = canvasHeight - bottomMargin;
+
+        if (y > maxAllowedHeight) {
+            y = y - bottomMargin;
+        } else if (y < heightTopMargin) {
+            y = y + heightTopMargin;
+        }
+        LOGGER.debug("Generated random position in canvas [{},{}]", x, y);
+
+        return new Point(x, y);
+    }
+
+    private int getRightMarginWidth() {
+        int canvasIconsOffset = 100;
+        final Dimension sideBarSize = compositionDetailSideBarComponent.getSize();
+        return sideBarSize.getWidth() + canvasIconsOffset;
+    }
+
+    private Point calculateOffsetFromCenter(final Point point) {
+        return calculateOffsetFromCenter(point.getX(), point.getY());
+    }
+
+    private Point calculateOffsetFromCenter(final int xPosition, final int yPosition) {
+        final int positionX = xPosition - canvasCenterX;
+        final int positionY = yPosition - canvasCenterY;
+        return new Point(positionX, positionY);
+    }
+
+    public ImmutablePair<Integer, Integer> getElementPositionByName(final String elementName) {
+        final String scriptJs = String.format(nodePositionJs, elementName);
+        final Object position = ((JavascriptExecutor) webDriver).executeScript(scriptJs);
+        final JsonObject positionAsJson = new JsonParser().parse(position.toString()).getAsJsonObject();
+        int xElement = positionAsJson.get("x").getAsInt();
+        int yElement = positionAsJson.get("y").getAsInt();
+        return new ImmutablePair<>(xElement, yElement);
+    }
+
+    /**
+     * Enum that contains identifiers and xpath expressions to elements related to the enclosing page object.
+     */
+    @AllArgsConstructor
+    private enum XpathSelector {
+        CANVAS_ELEMENT("canvas", "//*[@data-tests-id='%s']//canvas[1]");
+
+        @Getter
+        private final String id;
+        private final String xpathFormat;
+
+        public String getXpath() {
+            return String.format(xpathFormat, id);
+        }
+    }
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionDeploymentArtifactsTab.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionDeploymentArtifactsTab.java
new file mode 100644 (file)
index 0000000..2dbd6ba
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * Represents the composition page, details panel, Deployment Artifacts tab
+ */
+public class CompositionDeploymentArtifactsTab extends AbstractPageObject {
+
+    public CompositionDeploymentArtifactsTab(final WebDriver webDriver) {
+        super(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        waitForElementVisibility(By.xpath(XpathSelector.DEPLOYMENT_ARTIFACTS_TAB.getXpath()));
+    }
+
+    @AllArgsConstructor
+    @Getter
+    private enum XpathSelector {
+        DEPLOYMENT_ARTIFACTS_TAB("//artifacts-tab[.//header[contains(text(), 'Deployment Artifacts')]]");
+
+        private final String xpath;
+
+    }
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionDetailSideBarComponent.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionDetailSideBarComponent.java
new file mode 100644 (file)
index 0000000..214885a
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+public class CompositionDetailSideBarComponent extends AbstractPageObject {
+
+    private WebElement wrappingElement;
+
+    public CompositionDetailSideBarComponent(final WebDriver webDriver) {
+        super(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        wrappingElement = waitForElementVisibility(By.xpath(XpathSelector.MAIN_ELEMENT_DIV.getXpath()));
+    }
+
+    public String getSelectedComponentName() {
+        return wrappingElement.findElement(By.xpath(XpathSelector.DETAIL_COMPONENT_NAME_DIV.getXpath())).getText();
+    }
+
+    public void checkComponentIsSelected(final String componentName) {
+        assertThat("The selected component should be as expected", getSelectedComponentName(), is(componentName));
+    }
+
+    public Dimension getSize() {
+        final WebElement sideBarToggle = waitForElementVisibility(XpathSelector.DETAIL_SIDE_BAR_TOGGLE_DIV.getXpath());
+        if (!sideBarToggle.getAttribute("class").contains("active")) {
+            return new Dimension(0, 0);
+        }
+
+        return wrappingElement.getSize();
+    }
+
+    public AbstractPageObject selectTab(final CompositionDetailTabName tabName) {
+        final WebElement tabElement = wrappingElement.findElement(By.xpath(tabName.getXpathSelector().getXpath()));
+        tabElement.click();
+        switch (tabName) {
+            case INFORMATION:
+                return new CompositionInformationTab(webDriver);
+            case INPUTS:
+                return new CompositionInputsTab(webDriver);
+            case DEPLOYMENT_ARTIFACTS:
+                return new CompositionDeploymentArtifactsTab(webDriver);
+            case INFORMATIONAL_ARTIFACTS:
+                return new CompositionInformationalArtifactsTab(webDriver);
+            case API_ARTIFACTS:
+                return new CompositionApiArtifactsTab(webDriver);
+            case SUBSTITUTION_FILTER:
+                return new CompositionSubstitutionFilterTab(webDriver);
+            default:
+                throw new IllegalStateException("Not yet implemented: " + tabName);
+        }
+    }
+
+    /**
+     * Enum that contains identifiers and xpath expressions to elements related to the enclosing page object.
+     */
+    @AllArgsConstructor
+    private enum XpathSelector {
+        MAIN_ELEMENT_DIV("w-sdc-designer-sidebar", "//div[@class='%s']"),
+        DETAIL_SIDE_BAR_TOGGLE_DIV("w-sdc-designer-sidebar-toggle", "//div[contains(concat(' ',normalize-space(@class),' '),' %s ')]"),
+        DETAIL_HEADER("w-sdc-designer-sidebar-head", "//div[@data-tests-id='%s']"),
+        DETAIL_COMPONENT_NAME_DIV("selectedCompTitle", "//div[@data-tests-id='%s']"),
+        TAB_LIST("sdc-tabs-list", "//ul[@class='%s']/li"),
+        INFORMATION_TAB("detail-tab-information", "//li[@data-tests-id='%s']"),
+        INPUTS_TAB("detail-tab-inputs", "//li[@data-tests-id='%s']"),
+        DEPLOYMENT_ARTIFACTS_TAB("detail-tab-deployment-artifacts", "//li[@data-tests-id='%s']"),
+        INFORMATION_ARTIFACTS_TAB("detail-tab-information-artifacts", "//li[@data-tests-id='%s']"),
+        API_ARTIFACTS_TAB("detail-tab-api-artifacts", "//li[@data-tests-id='%s']"),
+        SUBSTITUTION_FILTER_TAB("detail-tab-substitution-filter", "//li[@data-tests-id='%s']");
+
+        @Getter
+        private final String id;
+        private final String xpathFormat;
+
+        public String getXpath() {
+            return String.format(xpathFormat, id);
+        }
+    }
+
+    @Getter
+    @AllArgsConstructor
+    public enum CompositionDetailTabName {
+        INFORMATION(XpathSelector.INFORMATION_TAB),
+        INPUTS(XpathSelector.INPUTS_TAB),
+        DEPLOYMENT_ARTIFACTS(XpathSelector.DEPLOYMENT_ARTIFACTS_TAB),
+        INFORMATIONAL_ARTIFACTS(XpathSelector.INFORMATION_ARTIFACTS_TAB),
+        API_ARTIFACTS(XpathSelector.API_ARTIFACTS_TAB),
+        SUBSTITUTION_FILTER(XpathSelector.SUBSTITUTION_FILTER_TAB);
+
+        private final XpathSelector xpathSelector;
+
+    }
+}
+
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionElementsComponent.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionElementsComponent.java
new file mode 100644 (file)
index 0000000..a4fc8b9
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import java.time.Duration;
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CompositionElementsComponent extends AbstractPageObject {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CompositionElementsComponent.class);
+
+    private WebElement wrappingElement;
+
+    public CompositionElementsComponent(final WebDriver webDriver) {
+        super(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        final By xpath = By.xpath(XpathSelector.MAIN_ELEMENT.getXpath());
+        wrappingElement = waitToBeClickable(xpath);
+    }
+
+    public Optional<WebElement> searchElement(final String elementName) {
+        final WebElement searchElementInput = wrappingElement.findElement(By.xpath(XpathSelector.SEARCH_INPUT.getXpath()));
+        searchElementInput.sendKeys(elementName);
+        new Actions(webDriver).pause(Duration.ofSeconds(1)).perform();
+        try {
+            final WebElement accordionElement = waitForElementVisibility("//div[@class='sdc-accordion-header']");
+            final String aClass = accordionElement.getAttribute("class");
+            if (!aClass.contains("open")) {
+                accordionElement.click();
+            }
+            return Optional.ofNullable(waitToBeClickable(XpathSelector.ELEMENT_DIV.getXpath(elementName)));
+        } catch (final Exception e) {
+            LOGGER.debug("Could not find element " + elementName, e);
+            return Optional.empty();
+        }
+    }
+
+    /**
+     * Enum that contains identifiers and xpath expressions to elements related to the enclosing page object.
+     */
+    @AllArgsConstructor
+    private enum XpathSelector {
+        MAIN_ELEMENT("composition-palette", "//composition-palette"),
+        SEARCH_INPUT("searchAsset-input", "//*[@data-tests-id='%s']"),
+        ELEMENT_DIV("//*[@data-tests-id='%s']");
+
+        @Getter
+        private String id;
+        private final String xpathFormat;
+
+        XpathSelector(final String xpathFormat) {
+            this.xpathFormat = xpathFormat;
+        }
+
+        public String getXpath() {
+            return String.format(xpathFormat, id);
+        }
+
+        public String getXpath(final String... params) {
+            return String.format(xpathFormat, params);
+        }
+    }
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionInformationTab.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionInformationTab.java
new file mode 100644 (file)
index 0000000..b911c63
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * Represents the composition page, details panel, Information tab
+ */
+public class CompositionInformationTab extends AbstractPageObject {
+
+    public CompositionInformationTab(final WebDriver webDriver) {
+        super(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        waitForElementVisibility(By.xpath(XpathSelector.INFORMATION_TAB.getXPath()));
+    }
+
+    @AllArgsConstructor
+    @Getter
+    private enum XpathSelector {
+        INFORMATION_TAB("//panel-info-tab");
+
+        private final String xPath;
+
+    }
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionInformationalArtifactsTab.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionInformationalArtifactsTab.java
new file mode 100644 (file)
index 0000000..401dd79
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * Represents the composition page, details panel, Informational Artifacts tab
+ */
+public class CompositionInformationalArtifactsTab extends AbstractPageObject {
+
+    public CompositionInformationalArtifactsTab(final WebDriver webDriver) {
+        super(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        waitForElementVisibility(By.xpath(XpathSelector.INFORMATIONAL_ARTIFACTS_TAB.getXpath()));
+    }
+
+    @AllArgsConstructor
+    @Getter
+    private enum XpathSelector {
+        INFORMATIONAL_ARTIFACTS_TAB("//artifacts-tab[.//header[contains(text(), 'Informational Artifacts')]]");
+
+        private final String xpath;
+
+    }
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionInputsTab.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionInputsTab.java
new file mode 100644 (file)
index 0000000..08f5074
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * Represents the composition page, details panel, Inputs tab
+ */
+public class CompositionInputsTab extends AbstractPageObject {
+
+    public CompositionInputsTab(final WebDriver webDriver) {
+        super(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        waitForElementVisibility(By.xpath(XpathSelector.INPUTS_TAB.getXPath()));
+    }
+
+    @AllArgsConstructor
+    @Getter
+    private enum XpathSelector {
+        INPUTS_TAB("//properties-tab[.//header[contains(text(), 'Inputs')]]");
+
+        private final String xPath;
+
+    }
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionPage.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionPage.java
new file mode 100644 (file)
index 0000000..c013247
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.onap.sdc.frontend.ci.tests.pages.ResourceWorkspaceTopBarComponent;
+import org.onap.sdc.frontend.ci.tests.pages.ServiceComponentPage;
+import org.onap.sdc.frontend.ci.tests.pages.TopNavComponent;
+import org.openecomp.sdc.be.model.ComponentInstance;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * Represents the Component (Service, VF, VFC, etc.) Composition Page
+ */
+public class CompositionPage extends AbstractPageObject {
+
+    private final ResourceWorkspaceTopBarComponent resourceWorkspaceTopBarComponent;
+    private final TopNavComponent topNavComponent;
+    private final CompositionElementsComponent compositionElementsComponent;
+    private final CompositionCanvasComponent compositionCanvasComponent;
+    private final CompositionDetailSideBarComponent compositionDetailSideBarComponent;
+
+    public CompositionPage(final WebDriver webDriver) {
+        super(webDriver);
+        topNavComponent = new TopNavComponent(webDriver);
+        resourceWorkspaceTopBarComponent = new ResourceWorkspaceTopBarComponent(webDriver);
+        compositionElementsComponent = new CompositionElementsComponent(webDriver);
+        compositionCanvasComponent = new CompositionCanvasComponent(webDriver);
+        compositionDetailSideBarComponent = new CompositionDetailSideBarComponent(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        compositionElementsComponent.isLoaded();
+        resourceWorkspaceTopBarComponent.isLoaded();
+        topNavComponent.isLoaded();
+        compositionCanvasComponent.isLoaded();
+        compositionDetailSideBarComponent.isLoaded();
+    }
+
+    public ComponentInstance addNodeToCompositionUsingApi(final String serviceName, final String serviceVersion, final String resourceName,
+                                                          final String resourceVersion) {
+        return compositionCanvasComponent.createNodeOnServiceCanvas(serviceName, serviceVersion, resourceName, resourceVersion);
+    }
+
+    /**
+     * Select a node in the canvas
+     *
+     * @param nodeName the node name to select
+     */
+    public void selectNode(final String nodeName) {
+        compositionCanvasComponent.selectNode(nodeName);
+        compositionDetailSideBarComponent.checkComponentIsSelected(nodeName);
+    }
+
+    public ServiceComponentPage goToServiceGeneral() {
+        topNavComponent.clickOnBreadCrumb(1);
+        return new ServiceComponentPage(webDriver);
+    }
+
+    /**
+     * Get the composition page detail sidebar component
+     *
+     * @return the composition detail sideBar component
+     */
+    public CompositionDetailSideBarComponent getDetailSideBar() {
+        return compositionDetailSideBarComponent;
+    }
+
+}
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionSubstitutionFilterTab.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/pages/component/workspace/CompositionSubstitutionFilterTab.java
new file mode 100644 (file)
index 0000000..d279e1f
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.pages.component.workspace;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * Represents the composition page, details panel, Substitution Filters tab
+ */
+public class CompositionSubstitutionFilterTab extends AbstractPageObject {
+
+    public CompositionSubstitutionFilterTab(final WebDriver webDriver) {
+        super(webDriver);
+    }
+
+    @Override
+    public void isLoaded() {
+        waitForElementVisibility(By.xpath(XpathSelector.SUBSTITUTION_FILTER_TAB.getXPath()));
+        waitForElementVisibility(By.xpath(XpathSelector.ADD_SUBSTITUTION_FILTER_BTN.getXPath()));
+    }
+
+    @AllArgsConstructor
+    @Getter
+    private enum XpathSelector {
+        SUBSTITUTION_FILTER_TAB("//substitution-filter-tab"),
+        ADD_SUBSTITUTION_FILTER_BTN("//button[@data-tests-id='add-substitution-filter-button']");
+
+        private final String xPath;
+
+    }
+}
index f39f21d..563dd11 100644 (file)
@@ -25,6 +25,10 @@ import static org.hamcrest.core.Is.is;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
+import org.onap.sdc.frontend.ci.tests.pages.ResourceCreatePage;
+import org.onap.sdc.frontend.ci.tests.pages.ResourceLeftSideMenu;
+import org.onap.sdc.frontend.ci.tests.pages.ResourceWorkspaceTopBarComponent;
+import org.onap.sdc.frontend.ci.tests.pages.ServiceComponentPage;
 import org.onap.sdc.frontend.ci.tests.pages.ServiceCreatePage;
 import org.onap.sdc.frontend.ci.tests.pages.TopNavComponent;
 import org.openqa.selenium.By;
@@ -47,7 +51,10 @@ public class HomePage extends AbstractPageObject {
 
     @Override
     public void isLoaded() {
+        waitToBeClickable(XpathSelector.HOME_RIGHT_CONTAINER.getXpath());
+        waitToBeClickable(XpathSelector.HOME_SIDE_BAR.getXpath());
         topNavComponent.isLoaded();
+        topNavComponent.waitRepositoryToBeClickable();
         assertThat("The Home tab should be selected", topNavComponent.isHomeSelected(), is(true));
     }
 
@@ -57,23 +64,98 @@ public class HomePage extends AbstractPageObject {
      * @return the following service create page
      */
     public ServiceCreatePage clickOnAddService() {
-        hoverToAddArea();
-        final By addServiceBtnLocator = By.xpath(XpathSelector.ADD_SERVICE_BTN.getXpath());
-        waitForElementVisibility(addServiceBtnLocator);
-        final WebElement addServiceBtn = findElement(addServiceBtnLocator);
-        addServiceBtn.click();
+        clickOnAdd(By.xpath(XpathSelector.ADD_SERVICE_BTN.getXpath()));
         return new ServiceCreatePage(webDriver);
     }
 
+    /**
+     * Clicks on the add VF button.
+     *
+     * @return the following resource create page
+     */
+    public ResourceCreatePage clickOnAddVf() {
+        clickOnAdd(By.xpath(XpathSelector.ADD_VF_BTN.getXpath()));
+        return new ResourceCreatePage(webDriver);
+    }
+
+    /**
+     * Clicks on the add CR button.
+     *
+     * @return the following resource create page
+     */
+    public ResourceCreatePage clickOnAddCr() {
+        clickOnAdd(By.xpath(XpathSelector.ADD_CR_BTN.getXpath()));
+        return new ResourceCreatePage(webDriver);
+    }
+
+    /**
+     * Clicks on the add PNF button.
+     *
+     * @return the following resource create page
+     */
+    public ResourceCreatePage clickOnAddPnf() {
+        clickOnAdd(By.xpath(XpathSelector.ADD_PNF_BTN.getXpath()));
+        return new ResourceCreatePage(webDriver);
+    }
+
+    /**
+     * Clicks on the Import VFC button.
+     *
+     * @return the following resource create page
+     */
+    public ResourceCreatePage clickOnImportVfc() {
+        clickOnImport(By.xpath(XpathSelector.IMPORT_VFC_BTN.getXpath()));
+        return new ResourceCreatePage(webDriver);
+    }
+
+    public AbstractPageObject clickOnComponent(final String component) {
+        WebElement element = waitForElementVisibility(By.xpath(XpathSelector.COMPONENT.getXpath(component)));
+        final WebElement componentTypeDiv = element.findElement(By.xpath("./../../../div[contains(@class, 'sdc-tile-header')]/div"));
+        final String text = componentTypeDiv.getText();
+        element.click();
+        if ("S".equals(text)) {
+            return new ServiceComponentPage(webDriver, topNavComponent,
+                new ResourceLeftSideMenu(webDriver), new ResourceWorkspaceTopBarComponent(webDriver));
+        }
+
+        throw new UnsupportedOperationException("Return not yet implemented for " + text);
+    }
+
+
+    private void clickOnAdd(final By locator) {
+        hoverToAddArea();
+        waitForElementVisibility(locator);
+        findElement(locator).click();
+    }
+
+    private void clickOnImport(final By locator) {
+        hoverToImportArea();
+        waitForElementVisibility(locator);
+        findElement(locator).click();
+    }
+
     /**
      * Hovers to the Add buttons area.
      *
      * @return the add buttons area element
      */
     public WebElement hoverToAddArea() {
+        return hoverTo(By.xpath(XpathSelector.ADD_BUTTONS_AREA.getXpath()));
+    }
+
+    /**
+     * Hovers to the Import buttons area.
+     *
+     * @return the Import buttons area element
+     */
+    public WebElement hoverToImportArea() {
+        return hoverTo(By.xpath(XpathSelector.IMPORT_BUTTONS_AREA.getXpath()));
+    }
+
+
+    private WebElement hoverTo(final By locator) {
+        final WebElement addButtonsAreaElement = findElement(locator);
         final Actions actions = new Actions(webDriver);
-        final By addButtonsAreaLocator = By.xpath(XpathSelector.ADD_BUTTONS_AREA.getXpath());
-        final WebElement addButtonsAreaElement = findElement(addButtonsAreaLocator);
         actions.moveToElement(addButtonsAreaElement).build().perform();
         return addButtonsAreaElement;
     }
@@ -83,15 +165,31 @@ public class HomePage extends AbstractPageObject {
      */
     @AllArgsConstructor
     private enum XpathSelector {
+        HOME_RIGHT_CONTAINER("w-sdc-main-right-container", "//div[@class='%s']"),
+        HOME_SIDE_BAR("w-sdc-left-sidebar", "//div[@class='%s']"),
         ADD_SERVICE_BTN("createServiceButton", "//*[@data-tests-id='%s']"),
-        ADD_BUTTONS_AREA("AddButtonsArea", "//*[@data-tests-id='%s']");
+        ADD_VF_BTN("createResourceButton", "//*[@data-tests-id='%s']"),
+        ADD_PNF_BTN("createPNFButton", "//*[@data-tests-id='%s']"),
+        ADD_CR_BTN("createCRButton", "//*[@data-tests-id='%s']"),
+        IMPORT_VFC_BTN("fileimportVFCbutton", "//*[@data-tests-id='%s']"),
+        ADD_BUTTONS_AREA("AddButtonsArea", "//*[@data-tests-id='%s']"),
+        IMPORT_BUTTONS_AREA("importButtonsArea", "//*[@data-tests-id='%s']"),
+        COMPONENT("//*[@data-tests-id='%s']");
 
         @Getter
-        private final String id;
+        private String id;
         private final String xpathFormat;
 
+        XpathSelector(final String xpathFormat) {
+            this.xpathFormat = xpathFormat;
+        }
+
         public String getXpath() {
             return String.format(xpathFormat, id);
         }
+
+        public String getXpath(String... parameters) {
+            return String.format(xpathFormat, parameters);
+        }
     }
 }
index 4149a91..d8f57db 100644 (file)
 
 package org.onap.sdc.frontend.ci.tests.utilities;
 
-import lombok.NoArgsConstructor;
-import org.onap.sdc.frontend.ci.tests.execute.setup.DriverFactory;
+import org.onap.sdc.frontend.ci.tests.pages.AbstractPageObject;
 import org.openqa.selenium.By;
 import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.support.ui.ExpectedConditions;
-import org.openqa.selenium.support.ui.WebDriverWait;
 
-@NoArgsConstructor
-public class LoaderHelper {
+public class LoaderHelper extends AbstractPageObject {
 
-    private WebDriver webDriver;
+    private final By loaderLocator = By.xpath("//*[@data-tests-id='loader' or @class='tlv-loader']");
 
     public LoaderHelper(final WebDriver webDriver) {
-        this.webDriver = webDriver;
+        super(webDriver);
     }
 
-    private final By locator = By.className("tlv-loader");
-
     public void waitForLoader(final int timeout) {
-        waitForLoaderVisibility(5);
-        waitForLoaderInvisibility(timeout);
+        waitForElementVisibility(loaderLocator, 5);
+        waitForElementInvisibility(loaderLocator, timeout);
     }
 
-    private void waitForLoaderVisibility(final int timeout) {
-        getWait(timeout)
-            .until(ExpectedConditions.visibilityOfElementLocated(locator));
-    }
+    @Override
+    public void isLoaded() {
 
-    private void waitForLoaderInvisibility(int timeout) {
-        getWait(timeout).until(ExpectedConditions.invisibilityOfElementLocated(locator));
     }
-
-    private WebDriverWait getWait(final int timeout) {
-        return new WebDriverWait(webDriver == null ? DriverFactory.getDriver() : webDriver, timeout);
-    }
-
 }
diff --git a/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/utilities/MouseUtils.java b/integration-tests/src/test/java/org/onap/sdc/frontend/ci/tests/utilities/MouseUtils.java
new file mode 100644 (file)
index 0000000..3bcca47
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.sdc.frontend.ci.tests.utilities;
+
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+
+public class MouseUtils {
+
+    private MouseUtils() {
+
+    }
+
+    /**
+     * Using Javascript, it creates a mouse pointer image that will follow the mouse pointer on screen, making the mouse visible to the user during
+     * the test.
+     *
+     * @param webDriver the selenium webdriver
+     * @see <a href="https://gist.github.com/primaryobjects/70087610d9aef0f4bddbe2101dda7649">github gist</a>
+     * @see <a href="https://stackoverflow.com/questions/35867776/visualize-show-mouse-cursor-position-in-selenium-2-tests-for-example-phpunit/35867777#35867777">stack
+     * overflow</a>
+     */
+    public static void enableMouseDebug(final WebDriver webDriver) {
+        final String mousePointerScript = "// Create mouse following image.\n"
+            + "var seleniumFollowerImg = document.createElement(\"img\");\n"
+            + "\n"
+            + "// Set image properties.\n"
+            + "seleniumFollowerImg.setAttribute('src', 'data:image/png;base64,'\n"
+            + "    + 'iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAQAAACGG/bgAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAA'\n"
+            + "    + 'HsYAAB7GAZEt8iwAAAAHdElNRQfgAwgMIwdxU/i7AAABZklEQVQ4y43TsU4UURSH8W+XmYwkS2I0'\n"
+            + "    + '9CRKpKGhsvIJjG9giQmliHFZlkUIGnEF7KTiCagpsYHWhoTQaiUUxLixYZb5KAAZZhbunu7O/PKf'\n"
+            + "    + 'e+fcA+/pqwb4DuximEqXhT4iI8dMpBWEsWsuGYdpZFttiLSSgTvhZ1W/SvfO1CvYdV1kPghV68a3'\n"
+            + "    + '0zzUWZH5pBqEui7dnqlFmLoq0gxC1XfGZdoLal2kea8ahLoqKXNAJQBT2yJzwUTVt0bS6ANqy1ga'\n"
+            + "    + 'VCEq/oVTtjji4hQVhhnlYBH4WIJV9vlkXLm+10R8oJb79Jl1j9UdazJRGpkrmNkSF9SOz2T71s7M'\n"
+            + "    + 'SIfD2lmmfjGSRz3hK8l4w1P+bah/HJLN0sys2JSMZQB+jKo6KSc8vLlLn5ikzF4268Wg2+pPOWW6'\n"
+            + "    + 'ONcpr3PrXy9VfS473M/D7H+TLmrqsXtOGctvxvMv2oVNP+Av0uHbzbxyJaywyUjx8TlnPY2YxqkD'\n"
+            + "    + 'dAAAAABJRU5ErkJggg==');\n"
+            + "seleniumFollowerImg.setAttribute('id', 'selenium_mouse_follower');\n"
+            + "seleniumFollowerImg.setAttribute('style', 'position: absolute; z-index: 99999999999; pointer-events: none;');\n"
+            + "\n"
+            + "// Add mouse follower to the web page.\n"
+            + "document.body.appendChild(seleniumFollowerImg);\n"
+            + "\n"
+            + "document.onmousemove = function(e) {\n"
+            + "  const mousePointer = document.getElementById('selenium_mouse_follower');\n"
+            + "  mousePointer.style.left = e.pageX + 'px';\n"
+            + "  mousePointer.style.top = e.pageY + 'px';\n"
+            + "}";
+
+        ((JavascriptExecutor) webDriver).executeScript(mousePointerScript);
+    }
+
+}