diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index ddbacc4c..a6c389e4 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -16,7 +16,6 @@
-
@@ -35,7 +34,6 @@
-
diff --git a/.travis.yml b/.travis.yml
index d62b80d4..466ae517 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,6 +24,7 @@ before_install:
- sudo rm /dev/random
- sudo mknod /dev/random c 1 9
# for gui tests
+ - export DBUS_SESSION_BUS_ADDRESS=/dev/null
- export CHROME_BIN=/usr/bin/google-chrome
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
@@ -33,9 +34,9 @@ before_install:
install:
#install and run tests and run style checking
- - mvn install -P travis
+ - mvn clean install -P travis
- cd ui-testing
- - mvn install -DskipTests
+ - mvn clean install -DskipTests
- cd ..
script:
@@ -67,8 +68,7 @@ script:
- sleep 30
- cd ../ui-testing
- - mvn test -P travis &
- - sleep 30
+ - mvn test -P travis
notifications:
slack: qreal-web:sT5qgA4qZZ9eyLI0yy2Mp81E
diff --git a/Travis/callTomcat.sh b/Travis/callTomcat.sh
new file mode 100755
index 00000000..bfc51400
--- /dev/null
+++ b/Travis/callTomcat.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+iter=1
+all=120
+until [ "$(curl --silent --show-error --connect-timeout 1 -I http://localhost:"${1:-"8080"}"/auth | grep '302 Found')" != "" ];
+do
+ if [ "$iter" -lt "$all" ]
+ then
+ echo "--- sleeping for 10 seconds"
+ sleep 10
+ let iter=$iter+1
+ else
+ echo "Server didn't return 302 found for long time"
+ exit 1
+ fi
+done
+echo "auth-service found"
+iter=1
+all=120
+until [ "$(curl --silent --show-error --connect-timeout 1 -I http://localhost:"${2:-"8082"}"/dashboard | grep '302 Found')" != "" ];
+do
+ if [ "$iter" -lt "$all" ]
+ then
+ echo "--- sleeping for 10 seconds"
+ sleep 10
+ let iter=$iter+1
+ else
+ echo "Server didn't return 302 found for long time"
+ exit 1
+ fi
+done
+echo "dashboard-service found"
+iter=1
+all=120
+until [ "$(curl --silent --show-error --connect-timeout 1 -I http://localhost:"${3:-"8081"}"/editor | grep '302 Found')" != "" ];
+do
+ if [ "$iter" -lt "$all" ]
+ then
+ echo "--- sleeping for 10 seconds"
+ sleep 10
+ let iter=$iter+1
+ else
+ echo "Server didn't return 302 found for long time"
+ exit 1
+ fi
+done
+echo "editor-service found"
+exit 0
diff --git a/Travis/checkstyle/checkstyle.xml b/Travis/checkstyle/checkstyle.xml
index e51a03d2..d4fa676d 100644
--- a/Travis/checkstyle/checkstyle.xml
+++ b/Travis/checkstyle/checkstyle.xml
@@ -29,7 +29,9 @@
-
+
+
+
@@ -68,9 +70,9 @@
-
+ value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
+
@@ -90,60 +92,60 @@
+ value="Package name ''{0}'' must match pattern ''{1}''."/>
+ value="Type name ''{0}'' must match pattern ''{1}''."/>
+ value="Member name ''{0}'' must match pattern ''{1}''."/>
+ value="Parameter name ''{0}'' must match pattern ''{1}''."/>
+ value="Local variable name ''{0}'' must match pattern ''{1}''."/>
+ value="Class type name ''{0}'' must match pattern ''{1}''."/>
+ value="Method type name ''{0}'' must match pattern ''{1}''."/>
+ value="Interface type name ''{0}'' must match pattern ''{1}''."/>
+ value="Method name ''{0}'' must match pattern ''{1}''."/>
-
-
-
+ value="GenericWhitespace ''{0}'' is followed by whitespace."/>
+
+
+
@@ -170,7 +172,7 @@
-
+
@@ -187,5 +189,6 @@
+
diff --git a/Travis/pmd/pmd-ruleset.xml b/Travis/pmd/pmd-ruleset.xml
index bef3725e..7e3a7df8 100644
--- a/Travis/pmd/pmd-ruleset.xml
+++ b/Travis/pmd/pmd-ruleset.xml
@@ -11,7 +11,9 @@
-
+
+
+
diff --git a/db-services/db-diagram-service/src/main/java/com/qreal/wmp/db/diagram/dao/DiagramDaoImpl.java b/db-services/db-diagram-service/src/main/java/com/qreal/wmp/db/diagram/dao/DiagramDaoImpl.java
index ace10fad..e949da8b 100644
--- a/db-services/db-diagram-service/src/main/java/com/qreal/wmp/db/diagram/dao/DiagramDaoImpl.java
+++ b/db-services/db-diagram-service/src/main/java/com/qreal/wmp/db/diagram/dao/DiagramDaoImpl.java
@@ -179,6 +179,10 @@ public void deleteFolder(Long folderId) throws AbortedException {
DiagramDaoImpl.class.getName());
}
Folder folder = (Folder) session.get(Folder.class, folderId);
+ for (Folder child : folder.getChildrenFolders()) {
+ deleteFolder(child.getId());
+ }
+ folder.remove();
session.delete(folder);
logger.trace("deleteFolder() successfully deleted a folder with id {}", folderId);
diff --git a/db-services/db-diagram-service/src/main/java/com/qreal/wmp/db/diagram/model/Folder.java b/db-services/db-diagram-service/src/main/java/com/qreal/wmp/db/diagram/model/Folder.java
index b3035249..f7f6b9f6 100644
--- a/db-services/db-diagram-service/src/main/java/com/qreal/wmp/db/diagram/model/Folder.java
+++ b/db-services/db-diagram-service/src/main/java/com/qreal/wmp/db/diagram/model/Folder.java
@@ -19,6 +19,8 @@
@ToString(exclude = "parentFolders")
public class Folder implements Serializable {
+ private static final String ROOT_FOLDER = "root";
+
@Id
@Column(name = "folder_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -35,10 +37,10 @@ public class Folder implements Serializable {
@Transient
private Long folderParentId;
- @ManyToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, mappedBy = "childrenFolders")
+ @ManyToMany(fetch = FetchType.EAGER, mappedBy = "childrenFolders")
private Set parentFolders = new HashSet<>();
- @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.EAGER)
@JoinTable(name = "folders_folders", joinColumns = {@JoinColumn(name = "parent_id")},
inverseJoinColumns = {@JoinColumn(name = "child_id")})
private Set childrenFolders = new HashSet<>();
@@ -95,7 +97,68 @@ public Folder(TFolder tFolder) {
diagrams = tFolder.getDiagrams().stream().map(Diagram::new).collect(Collectors.toSet());
}
}
-
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public void setFolderName(String folderName) {
+ this.folderName = folderName;
+ }
+
+ public void remove() {
+ parentFolders.forEach(x -> x.removeChild(id));
+ parentFolders.clear();
+ }
+
+ public Set getChildrenFolders() {
+ return childrenFolders;
+ }
+
+ public Set getOwners() {
+ return owners;
+ }
+
+ public void setOwners(Set owners) {
+ this.owners = owners;
+ }
+
+ public Long getFolderParentId() {
+ return folderParentId;
+ }
+
+ public void setFolderParentId(Long folderParentId) {
+ this.folderParentId = folderParentId;
+ }
+
+ public Set getParentFolders() {
+ return parentFolders;
+ }
+
+ public void setParentFolders(Set parentFolders) {
+ this.parentFolders = parentFolders;
+ }
+
+ public void setChildrenFolders(Set childrenFolders) {
+ this.childrenFolders = childrenFolders;
+ }
+
+ public Set getDiagrams() {
+ return diagrams;
+ }
+
+ public void setDiagrams(Set diagrams) {
+ this.diagrams = diagrams;
+ }
+
+ public String getFolderName() {
+ return folderName;
+ }
+
/** Converter from Folder to Thrift TFolder.*/
public TFolder toTFolder(final String username) {
TFolder tFolder = new TFolder();
@@ -134,5 +197,9 @@ private void setFolderParentId(String username, TFolder tFolder) {
tFolder.setFolderParentId(folderParentId);
}
}
+
+ private void removeChild(long childId) {
+ childrenFolders.stream().filter(x -> x.id == childId).findFirst().ifPresent(x -> childrenFolders.remove(x));
+ }
}
diff --git a/editor-core/src/main/webapp/app/core/editorCore/controller/SceneController.ts b/editor-core/src/main/webapp/app/core/editorCore/controller/SceneController.ts
index 669fe7ab..cd0ce699 100644
--- a/editor-core/src/main/webapp/app/core/editorCore/controller/SceneController.ts
+++ b/editor-core/src/main/webapp/app/core/editorCore/controller/SceneController.ts
@@ -10,6 +10,7 @@ import {DiagramElement} from "../model/DiagramElement";
import {SubprogramNode} from "../model/SubprogramNode";
import {Property} from "../model/Property";
import {NodeType} from "../model/NodeType";
+import {Scroller, Direction} from "../model/Scroller";
import {DiagramElementListener} from "./DiagramElementListener";
import {SceneCommandFactory} from "../model/commands/SceneCommandFactory";
import {DiagramEditorController} from "./DiagramEditorController";
@@ -21,8 +22,10 @@ export class SceneController {
private currentElement: DiagramElement;
private clickFlag : boolean;
private rightClickFlag : boolean;
+ private scroller : Scroller;
private undoRedoController: UndoRedoController;
private lastCellMouseDownPosition: {x: number, y: number};
+ private lastCellScrollPosition: {x: number, y: number};
private lastCellMouseDownSize: {width: number, height: number};
private paperCommandFactory: SceneCommandFactory;
private contextMenuId = "scene-context-menu";
@@ -34,8 +37,10 @@ export class SceneController {
this.paperCommandFactory = new SceneCommandFactory(this);
this.clickFlag = false;
this.rightClickFlag = false;
+ this.scroller = new Scroller();
this.lastCellMouseDownPosition = { x: 0, y: 0 };
this.lastCellMouseDownSize = { width: 0, height: 0 };
+ this.lastCellScrollPosition = { x: 0, y: 0 };
this.scene.on('cell:pointerdown', (cellView, event, x, y): void => {
this.cellPointerdownListener(cellView, event, x, y);
@@ -52,10 +57,12 @@ export class SceneController {
});
this.diagramEditorController.getGraph().on('change:position', (cell) => {
- if (!this.rightClickFlag) {
- return;
+ if (this.scroller.getScroll()) {
+ cell.set('position', this.lastCellScrollPosition);
+ }
+ if (this.rightClickFlag) {
+ cell.set('position', cell.previous('position'));
}
- cell.set('position', cell.previous('position'));
});
this.initDropPaletteElementListener();
@@ -247,7 +254,7 @@ export class SceneController {
this.changeCurrentElement(element);
if (this.scene.getNodeById(cellView.model.id) && event.button == MouseButton.left) {
- var node:DiagramNode = this.scene.getNodeById(cellView.model.id);
+ var node: DiagramNode = this.scene.getNodeById(cellView.model.id);
this.lastCellMouseDownPosition.x = node.getX();
this.lastCellMouseDownPosition.y = node.getY();
var bbox = cellView.getBBox();
@@ -270,6 +277,7 @@ export class SceneController {
});
} else if (event.button == MouseButton.left){
+ this.borderUnCrossed();
var node: DiagramNode = this.scene.getNodeById(cellView.model.id);
if (node) {
if (node.isResizing()) {
@@ -300,6 +308,11 @@ export class SceneController {
}
private cellPointermoveListener(cellView, event, x, y): void {
+ var element: DiagramElement = this.scene.getNodeById(cellView.model.id) ||
+ this.scene.getLinkById(cellView.model.id);
+ if (element instanceof DefaultDiagramNode) {
+ this.checkBorder(element, cellView, event)
+ }
this.clickFlag = false;
var node: DiagramNode = this.scene.getNodeById(cellView.model.id);
if (node) {
@@ -407,6 +420,121 @@ export class SceneController {
});
}
+ private checkBorder(element: DiagramElement, cellView, event) : void {
+ var sceneWrapper: HTMLDivElement = $(".scene-wrapper")[0];
+ var boundingBox: any = sceneWrapper.getBoundingClientRect();
+
+ var node = this.scene.getNodeById(cellView.model.id);
+ this.borderUnCrossed();
+ if (event.pageX + this.scene.getGridSize() * this.scene.getZoom() >= boundingBox.right) {
+ this.scroller.setDirection(Direction.Right);
+ this.borderCrossed(node, event, cellView);
+ } else if (event.pageX - this.scene.getGridSize() * this.scene.getZoom() <= boundingBox.left) {
+ this.scroller.setDirection(Direction.Left);
+ this.borderCrossed(node, event, cellView);
+ } else if (event.pageY + this.scene.getGridSize() * this.scene.getZoom() >= boundingBox.bottom) {
+ this.scroller.setDirection(Direction.Down);
+ this.borderCrossed(node, event, cellView);
+ } else if (event.pageY - this.scene.getGridSize() * this.scene.getZoom() <= boundingBox.top) {
+ this.scroller.setDirection(Direction.Up);
+ this.borderCrossed(node, event, cellView);
+ }
+ this.updateLastCellScrollPosition(event);
+ }
+
+ private borderCrossed(node: DiagramNode, event, cellView): void {
+ this.scroller.setScroll(true);
+ var that = this;
+ switch (this.scroller.getDirection()) {
+ case Direction.Right: {
+ this.scroller.setIntervalId(setInterval(() => that.scrollRight(node, event, cellView), 150));
+ break;
+ }
+ case Direction.Left: {
+ this.scroller.setIntervalId(setInterval(() => that.scrollLeft(node, event, cellView), 150));
+ break;
+ }
+ case Direction.Down: {
+ this.scroller.setIntervalId(setInterval(() => that.scrollBottom(node, event, cellView), 150));
+ break;
+ }
+ case Direction.Up: {
+ this.scroller.setIntervalId(setInterval(() => that.scrollTop(node, event, cellView), 150));
+ break;
+ }
+ }
+ }
+
+ private borderUnCrossed(): void {
+ this.scroller.setDirection(Direction.None);
+ if (this.scroller.getIntervalId() != -1) {
+ clearInterval(this.scroller.getIntervalId());
+ this.scroller.setIntervalId(-1);
+ this.scroller.setScroll(false);
+ }
+ }
+
+ private scrollRight(node: DiagramNode, event, cellView) : void {
+ var sceneWrapper : HTMLDivElement = ( $(".scene-wrapper")[0]);
+ sceneWrapper.scrollLeft += this.scene.getGridSize() * this.scene.getZoom();
+ if (node.getX() + 3 * this.scene.getGridSize() <= DiagramScene.WIDTH) {
+ this.updateLastCellScrollPosition(event);
+ node.setPosition(
+ this.lastCellScrollPosition.x,
+ this.lastCellScrollPosition.y,
+ this.scene.getZoom(),
+ cellView);
+ }
+ }
+
+ private scrollLeft(node: DiagramNode, event, cellView) : void {
+ ( $(".scene-wrapper")[0]).scrollLeft -= this.scene.getGridSize() * this.scene.getZoom();
+ if (node.getX() >= this.scene.getGridSize()) {
+ this.updateLastCellScrollPosition(event);
+ node.setPosition(
+ this.lastCellScrollPosition.x,
+ this.lastCellScrollPosition.y,
+ this.scene.getZoom(),
+ cellView);
+ }
+ }
+
+ private scrollBottom(node: DiagramNode, event, cellView) : void {
+ ( $(".scene-wrapper")[0]).scrollTop += this.scene.getGridSize() * this.scene.getZoom();
+ if (node.getY() + 3 * this.scene.getGridSize() <= DiagramScene.HEIGHT) {
+ this.updateLastCellScrollPosition(event);
+ node.setPosition(
+ this.lastCellScrollPosition.x,
+ this.lastCellScrollPosition.y,
+ this.scene.getZoom(),
+ cellView);
+ }
+ }
+
+ private scrollTop(node: DiagramNode, event, cellView) : void {
+ ( $(".scene-wrapper")[0]).scrollTop -= this.scene.getGridSize() * this.scene.getZoom();
+ if (node.getY() >= this.scene.getGridSize()) {
+ this.updateLastCellScrollPosition(event);
+ node.setPosition(
+ this.lastCellScrollPosition.x,
+ this.lastCellScrollPosition.y,
+ this.scene.getZoom(),
+ cellView);
+ }
+ }
+
+ private updateLastCellScrollPosition(event) : void {
+ var offsetX = (event.pageX - $("#" + this.scene.getId()).offset().left +
+ $("#" + this.scene.getId()).scrollLeft()) / this.scene.getZoom();
+ var offsetY = (event.pageY - $("#" + this.scene.getId()).offset().top +
+ $("#" + this.scene.getId()).scrollTop()) / this.scene.getZoom();
+ var gridSize: number = this.scene.getGridSize();
+ offsetX -= offsetX % gridSize;
+ offsetY -= offsetY % gridSize;
+ this.lastCellScrollPosition.x = Math.min(offsetX, DiagramScene.WIDTH - 2 * this.scene.getGridSize());
+ this.lastCellScrollPosition.y = Math.min(offsetY, DiagramScene.HEIGHT - 2 * this.scene.getGridSize());
+ }
+
private getElementBelow(event: any, checker?: (cell: any) => boolean) {
var diagramPaper: HTMLDivElement = document.getElementById(this.scene.getId());
return this.diagramEditorController.getGraph().get('cells').find((cell) => {
diff --git a/editor-core/src/main/webapp/app/core/editorCore/model/DiagramScene.ts b/editor-core/src/main/webapp/app/core/editorCore/model/DiagramScene.ts
index 31c36ba4..4bcdb4e7 100644
--- a/editor-core/src/main/webapp/app/core/editorCore/model/DiagramScene.ts
+++ b/editor-core/src/main/webapp/app/core/editorCore/model/DiagramScene.ts
@@ -4,6 +4,9 @@ import {SubprogramNode} from "./SubprogramNode";
import {DiagramElementListener} from "../controller/DiagramElementListener";
export class DiagramScene extends joint.dia.Paper {
+ public static get WIDTH(): number {return 2000;}
+ public static get HEIGHT(): number {return 2000;}
+
private htmlId: string;
private graph: joint.dia.Graph;
private currentLinkType: string;
@@ -20,8 +23,8 @@ export class DiagramScene extends joint.dia.Paper {
super({
el: $('#' + htmlId),
- width: 2000,
- height: 2000,
+ width: DiagramScene.WIDTH,
+ height: DiagramScene.HEIGHT,
model: graph,
gridSize: gridSize,
defaultLink: new joint.dia.Link({
diff --git a/editor-core/src/main/webapp/app/core/editorCore/model/Scroller.ts b/editor-core/src/main/webapp/app/core/editorCore/model/Scroller.ts
new file mode 100644
index 00000000..f19bc18a
--- /dev/null
+++ b/editor-core/src/main/webapp/app/core/editorCore/model/Scroller.ts
@@ -0,0 +1,42 @@
+export enum Direction {
+ Up, Down, Left, Right, None
+}
+
+export class Scroller {
+
+ private scroll: boolean;
+
+ private intervalId: number;
+
+ private direction: Direction;
+
+ constructor() {
+ this.scroll = false;
+ this.direction = Direction.None;
+ }
+
+ public getDirection(): Direction {
+ return this.direction;
+ }
+
+ public setDirection(value: Direction) {
+ this.direction = value;
+ }
+
+ public getIntervalId(): number {
+ return this.intervalId;
+ }
+
+ public setIntervalId(value: number) {
+ this.intervalId = value;
+ }
+
+ public getScroll(): boolean {
+ return this.scroll;
+ }
+
+ public setScroll(value: boolean) {
+ this.scroll = value;
+ }
+
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index b8015ce8..7c106d84 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,8 @@
8080
8082
/editor
+ /editor/robots
+ /editor/bpmn
/auth
/dashboard
/editorRest
@@ -53,6 +55,8 @@
8080
8080
/editor
+ /editor/robots
+ /editor/bpmn
/auth
/dashboard
/editorRest
@@ -70,6 +74,8 @@
8080
8082
/editor
+ /editor/robots
+ /editor/bpmn
/auth
/dashboard
/editorRest
diff --git a/ui-testing/pom.xml b/ui-testing/pom.xml
index 4853448c..8775fe7a 100644
--- a/ui-testing/pom.xml
+++ b/ui-testing/pom.xml
@@ -5,7 +5,7 @@
4.0.0
groupId
- UI-testing
+ ui-testing
1.0-SNAPSHOT
@@ -52,6 +52,7 @@
exec-maven-plugin
org.codehaus.mojo
+ 1.5.0
Check services
@@ -99,17 +100,61 @@
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ 1.6.0
+
+
com.codeborne
selenide
4.0
- test
-
+
- io.github.bonigarcia
- webdrivermanager
- 1.4.10
+ org.apache.commons
+ commons-lang3
+ 3.5
+
+
+
+ org.jsoup
+ jsoup
+ 1.10.2
+
+
+
+
+ org.springframework
+ spring-beans
+ ${springframework.version}
+
+
+ org.springframework
+ spring-context
+ ${springframework.version}
+
+
+ org.springframework
+ spring-webmvc
+ ${springframework.version}
+
+
+ org.springframework
+ spring-tx
+ ${springframework.version}
+
+
+ org.springframework.security
+ spring-security-taglibs
+ ${springframework.security.version}
+
+
+ org.springframework
+ spring-test
+ ${springframework.version}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/Page.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/Page.java
new file mode 100644
index 00000000..834358bd
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/Page.java
@@ -0,0 +1,43 @@
+package com.qreal.wmp.uitesting;
+
+/** Describes WMP pages in browser. */
+@SuppressWarnings("unchecked")
+public enum Page {
+
+ Auth("auth") {
+ @Override
+ public T getInstance(PageFactory pageFactory) {
+ return (T) pageFactory.getAuthPage();
+ }
+ },
+ Dashboard("dashboard") {
+ @Override
+ public T getInstance(PageFactory pageFactory) {
+ return (T) pageFactory.getDashboardPage();
+ }
+ },
+ EditorRobots("robotsEditor") {
+ @Override
+ public T getInstance(PageFactory pageFactory) {
+ return (T) pageFactory.getEditorPage();
+ }
+ },
+ EditorBPMN("bpmnEditor") {
+ @Override
+ public T getInstance(PageFactory pageFactory) {
+ return (T) pageFactory.getEditorPage();
+ }
+ };
+
+ private String name;
+
+ Page(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public abstract T getInstance(PageFactory pageFactory);
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageFactory.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageFactory.java
new file mode 100644
index 00000000..2c6f89b6
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageFactory.java
@@ -0,0 +1,56 @@
+package com.qreal.wmp.uitesting;
+
+import com.qreal.wmp.uitesting.dia.palette.PaletteImpl;
+import com.qreal.wmp.uitesting.dia.property.PropertyEditorImpl;
+import com.qreal.wmp.uitesting.dia.scene.SceneImpl;
+import com.qreal.wmp.uitesting.headerpanel.EditorHeaderPanelImpl;
+import com.qreal.wmp.uitesting.pages.AuthPage;
+import com.qreal.wmp.uitesting.pages.DashboardPage;
+import com.qreal.wmp.uitesting.pages.EditorPage;
+import com.qreal.wmp.uitesting.pages.EventProvider;
+import org.openqa.selenium.WebDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.codeborne.selenide.Selenide.title;
+
+/** Returns page instance for requested uri. */
+public class PageFactory {
+
+ private static final Logger logger = LoggerFactory.getLogger(PageFactory.class);
+
+ private final WebDriver webDriver;
+
+ private final EventProvider eventProvider;
+
+ public PageFactory(WebDriver webDriver) {
+ this.webDriver = webDriver;
+ eventProvider = new EventProvider();
+ }
+
+ /** Returns Editor Page instance. */
+ public EditorPage getEditorPage() {
+ logger.info("Editor page was created");
+ EditorPage page = new EditorPage(
+ title(),
+ SceneImpl.getScene(webDriver),
+ PaletteImpl.getPalette(),
+ PropertyEditorImpl.getPropertyEditor(),
+ EditorHeaderPanelImpl.getEditorHeaderPanel(this, webDriver, eventProvider)
+ );
+ eventProvider.addListener(page);
+ return page;
+ }
+
+ /** Returns Dashboard Page instance. */
+ public DashboardPage getDashboardPage() {
+ logger.info("Dashboard page was created");
+ return new DashboardPage(title());
+ }
+
+ /** Returns Auth Page instance. */
+ public AuthPage getAuthPage() {
+ logger.info("Auth page was created");
+ return new AuthPage(title());
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageLoader.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageLoader.java
new file mode 100644
index 00000000..eb94332e
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageLoader.java
@@ -0,0 +1,42 @@
+package com.qreal.wmp.uitesting;
+
+import com.qreal.wmp.uitesting.exceptions.WrongAuthException;
+import com.qreal.wmp.uitesting.services.Auther;
+import com.qreal.wmp.uitesting.services.Opener;
+
+/**
+ * Loads page.
+ * It means, firstly, it opens uri by Opener service.
+ * Secondly, it returns page by PageFactory.
+ */
+public class PageLoader {
+
+ private final PageFactory pageFactory;
+
+ private final Opener opener;
+
+ private final Auther auther;
+
+ public PageLoader(PageFactory pageFactory, Opener opener, Auther auther) {
+ this.pageFactory = pageFactory;
+ this.opener = opener;
+ this.auther = auther;
+ }
+
+ /** Loads and returns requested page with default authentication. */
+ public T load(Page page) {
+ opener.open(page.getName());
+ return getPage(page);
+ }
+
+ /** Loads and returns requested page with login and password. */
+ public T load(Page page, String username, String password) throws WrongAuthException {
+ auther.auth(username, password);
+ opener.open(page.getName());
+ return getPage(page);
+ }
+
+ private T getPage(Page page) {
+ return page.getInstance(pageFactory);
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/AppInit.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/AppInit.java
new file mode 100644
index 00000000..179b4bda
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/AppInit.java
@@ -0,0 +1,18 @@
+package com.qreal.wmp.uitesting.config;
+
+import io.github.bonigarcia.wdm.ChromeDriverManager;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+
+@ComponentScan("com.qreal.wmp.uitesting")
+public class AppInit {
+
+ /** Main function creates context. */
+ public static void main(final String... args) {
+ ChromeDriverManager.getInstance().setup();
+ final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ context.scan("com.qreal.wmp.uitesting");
+ context.register(AppInit.class);
+ context.refresh();
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/DevConfig.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/DevConfig.java
new file mode 100644
index 00000000..5632aa9a
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/DevConfig.java
@@ -0,0 +1,67 @@
+package com.qreal.wmp.uitesting.config;
+
+import com.codeborne.selenide.WebDriverRunner;
+import com.qreal.wmp.uitesting.PageFactory;
+import com.qreal.wmp.uitesting.PageLoader;
+import com.qreal.wmp.uitesting.services.Auther;
+import com.qreal.wmp.uitesting.services.Opener;
+import com.qreal.wmp.uitesting.services.impl.AutherImpl;
+import com.qreal.wmp.uitesting.services.impl.OpenerImpl;
+import io.github.bonigarcia.wdm.ChromeDriverManager;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.core.env.Environment;
+
+import java.util.concurrent.TimeUnit;
+
+/** Creates beans for Spring needs. **/
+@Configuration
+@PropertySource("classpath:pages.properties")
+public class DevConfig {
+
+ @Autowired
+ private Environment environment;
+
+ /** Processor for Environment linked to property files.*/
+ @Bean
+ public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
+ return new PropertySourcesPlaceholderConfigurer();
+ }
+
+ @Bean
+ public WebDriver webDriver() {
+ ChromeDriverManager.getInstance().setup();
+ WebDriver driver = new ChromeDriver();
+ driver.manage().window().maximize();
+ driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
+ driver.manage().timeouts().setScriptTimeout(3, TimeUnit.SECONDS);
+ driver.manage().timeouts().pageLoadTimeout(20, TimeUnit.SECONDS);
+ WebDriverRunner.setWebDriver(driver);
+ return driver;
+ }
+
+ @Bean
+ public Auther auther() {
+ return new AutherImpl(environment);
+ }
+
+ @Bean
+ public Opener opener() {
+ return new OpenerImpl(environment, auther());
+ }
+
+ @Bean
+ public PageFactory pageFactory() {
+ return new PageFactory(webDriver());
+ }
+
+ @Bean
+ public PageLoader pageLoader() {
+ return new PageLoader(pageFactory(), opener(), auther());
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/Palette.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/Palette.java
new file mode 100644
index 00000000..111c0f43
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/Palette.java
@@ -0,0 +1,18 @@
+package com.qreal.wmp.uitesting.dia.palette;
+
+import org.openqa.selenium.NoSuchElementException;
+
+/**
+ * Describes Palette.
+ * For any manipulating with it.
+ */
+public interface Palette {
+
+ /**
+ * Chooses an element from Palette.
+ *
+ * @param elementName name of block
+ * @return block
+ */
+ PaletteElement getElement(String elementName) throws NoSuchElementException;
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteElement.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteElement.java
new file mode 100644
index 00000000..6ae9e640
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteElement.java
@@ -0,0 +1,25 @@
+package com.qreal.wmp.uitesting.dia.palette;
+
+import com.codeborne.selenide.SelenideElement;
+
+
+/** Describes palette's items. */
+public class PaletteElement {
+
+ private final SelenideElement innerSeleniumELement;
+
+ private final String name;
+
+ public PaletteElement(SelenideElement innerSeleniumELement) {
+ this.innerSeleniumELement = innerSeleniumELement;
+ name = innerSeleniumELement.attr("data-type");
+ }
+
+ public SelenideElement getInnerSeleniumELement() {
+ return innerSeleniumELement;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteImpl.java
new file mode 100644
index 00000000..8f3d1ef3
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteImpl.java
@@ -0,0 +1,28 @@
+package com.qreal.wmp.uitesting.dia.palette;
+
+import com.codeborne.selenide.SelenideElement;
+import org.openqa.selenium.By;
+import org.openqa.selenium.NoSuchElementException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.codeborne.selenide.Selectors.withText;
+import static com.codeborne.selenide.Selenide.$;
+
+/** {@inheritDoc} */
+public class PaletteImpl implements Palette {
+
+ private static final String SELECTOR = "#palette-tab-content";
+
+ private static final Logger logger = LoggerFactory.getLogger(PaletteImpl.class);
+
+ public PaletteElement getElement(final String elementName) throws NoSuchElementException {
+ final SelenideElement element = $(By.cssSelector(SELECTOR)).find(withText(elementName));
+ logger.info("Get element {} from Palette", element);
+ return new PaletteElement(element);
+ }
+
+ public static Palette getPalette() {
+ return new PaletteImpl();
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditor.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditor.java
new file mode 100644
index 00000000..1d96ef40
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditor.java
@@ -0,0 +1,16 @@
+package com.qreal.wmp.uitesting.dia.property;
+
+import com.codeborne.selenide.SelenideElement;
+import org.openqa.selenium.NoSuchElementException;
+
+/**
+ * Describe Property Editor.
+ * When block is clicked, it could be configured by Property Editor.
+ */
+public interface PropertyEditor {
+ /** Set property of element which on the focus. */
+ void setProperty(SelenideElement element, String propertyName, String propertyValue) throws NoSuchElementException;
+
+ /** Return the value of property by name. */
+ String getProperty(final SelenideElement element, final String propertyName) throws NoSuchElementException;
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditorImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditorImpl.java
new file mode 100644
index 00000000..1200616e
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditorImpl.java
@@ -0,0 +1,67 @@
+package com.qreal.wmp.uitesting.dia.property;
+
+import com.codeborne.selenide.SelenideElement;
+import org.openqa.selenium.By;
+import org.openqa.selenium.NoSuchElementException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.IntStream;
+
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
+
+/** {@inheritDoc} */
+public class PropertyEditorImpl implements PropertyEditor {
+
+ private static final String SELECTOR = "#property_table";
+
+ private static final Logger logger = LoggerFactory.getLogger(PropertyEditorImpl.class);
+
+ /** {@inheritDoc} */
+ public void setProperty(final SelenideElement element, final String propertyName, final String propertyValue)
+ throws NoSuchElementException
+ {
+ element.click();
+ SelenideElement property = getInputOfElement(propertyName);
+ if (property.attr("class").equals("input-group")) {
+ property.find(By.xpath(".//*")).setValue(propertyValue);
+ } else {
+ property.selectOptionByValue(propertyValue);
+ }
+ logger.info("Set property {} to {}", propertyName, propertyValue);
+ }
+
+ /** {@inheritDoc} */
+ public String getProperty(final SelenideElement element, final String propertyName) throws NoSuchElementException {
+ $(By.cssSelector(SELECTOR)).click();
+ element.click();
+ SelenideElement property = getInputOfElement(propertyName);
+ logger.info("Get value of preperty {}", propertyName);
+ if (property.attr("class").equals("input-group")) {
+ return property.find(By.xpath(".//*")).getValue();
+ } else {
+ return property.getSelectedOption().getValue();
+ }
+ }
+
+ public static PropertyEditor getPropertyEditor() {
+ return new PropertyEditorImpl();
+ }
+
+ /** To set/get property we need to take web element which describes needed field. */
+ private SelenideElement getInputOfElement(final String propertyName) {
+ final List allChilds = $$(By.cssSelector(SELECTOR + " tbody > * > *"));
+ final OptionalInt indexOfNeeded = IntStream.range(0, allChilds.size())
+ .filter(index -> allChilds.get(index).getText().contains(propertyName))
+ .findFirst();
+ if (!indexOfNeeded.isPresent()) {
+ throw new NoSuchElementException("There is no property with name " + propertyName);
+ }
+ return $(By.cssSelector(SELECTOR + " tbody > *:nth-of-type("
+ + (indexOfNeeded.getAsInt() / 2 + 1)
+ + ") > *:nth-of-type(2) > *" ));
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Coordinate.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Coordinate.java
new file mode 100644
index 00000000..d6d08559
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Coordinate.java
@@ -0,0 +1,64 @@
+package com.qreal.wmp.uitesting.dia.scene;
+
+import com.codeborne.selenide.SelenideElement;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Optional;
+
+/**
+ * Describes element's position on the Scene.
+ * Contains absolute coordinates of scene, which are written in 'transform' tag on the html representation.
+ * Also contains cell's position (the Scene is represented by a mesh of cells)
+ */
+public class Coordinate {
+
+ public static final String SELECTOR = "transform";
+
+ public static final int POINT_IN_CELL = 25;
+
+ private final int xAbsolute;
+
+ private final int yAbsolute;
+
+ /** Returns coordinate of object on scene. */
+ @NotNull
+ public static Optional getCoordinateFromSeleniumObject(SelenideElement element) {
+ final String position = element.attr(SELECTOR);
+ final String[] pairStr = position.substring(position.indexOf('(') + 1, position.indexOf(')')).split(",");
+ return Optional.of(
+ new Coordinate(
+ Double.valueOf(pairStr[0]).intValue(),
+ Double.valueOf(pairStr[1]).intValue()
+ )
+ );
+ }
+
+ public Coordinate(int xAbsolute, int yAbsolute) {
+ this.xAbsolute = xAbsolute;
+ this.yAbsolute = yAbsolute;
+ }
+
+ public int getXCell() {
+ return xAbsolute / POINT_IN_CELL;
+ }
+
+ public int getYCell() {
+ return yAbsolute / POINT_IN_CELL;
+ }
+
+ public int getXAbsolute() {
+ return xAbsolute;
+ }
+
+ public int getYAbsolute() {
+ return yAbsolute;
+ }
+
+ public boolean equals(final Coordinate other) {
+ return xAbsolute == other.getXAbsolute() && yAbsolute == other.getYAbsolute();
+ }
+
+ public String toString() {
+ return "(" + getXAbsolute() + "," + getYAbsolute() + ")";
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Scene.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Scene.java
new file mode 100644
index 00000000..778dfe85
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Scene.java
@@ -0,0 +1,35 @@
+package com.qreal.wmp.uitesting.dia.scene;
+
+import com.qreal.wmp.uitesting.dia.palette.PaletteElement;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.elements.Link;
+import com.qreal.wmp.uitesting.dia.scene.elements.SceneElement;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+
+import java.util.List;
+
+public interface Scene {
+ /** Drag element from scene or palette and put it on the center of scene. */
+ Block dragAndDrop(PaletteElement paletteElement);
+
+ /** Drag element from scene or palette and put it in cell of the scene. */
+ Block dragAndDrop(PaletteElement element, int cellX, int cellY);
+
+ /** Move element from scene to the cell. */
+ void moveToCell(Block block, int cellX, int cellY);
+
+ /** Check if element exist on the scene. */
+ boolean exist(SceneElement element);
+
+ /** Remove block from the scene. */
+ void remove(SceneElement element) throws ElementNotOnTheSceneException;
+
+ /** Add link between two elements. */
+ Link addLink(Block source, Block target);
+
+ /** Return all blocks. */
+ List getBlocks();
+
+ /** Remove all elements from the scene. */
+ void clean();
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/SceneImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/SceneImpl.java
new file mode 100644
index 00000000..28480459
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/SceneImpl.java
@@ -0,0 +1,173 @@
+package com.qreal.wmp.uitesting.dia.scene;
+
+import com.codeborne.selenide.Condition;
+import com.codeborne.selenide.SelenideElement;
+import com.google.common.base.Predicate;
+import com.qreal.wmp.uitesting.dia.palette.PaletteElement;
+import com.qreal.wmp.uitesting.dia.palette.PaletteImpl;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.elements.Link;
+import com.qreal.wmp.uitesting.dia.scene.elements.SceneElement;
+import com.qreal.wmp.uitesting.dia.scene.providers.BlockProvider;
+import com.qreal.wmp.uitesting.dia.scene.providers.LinkProvider;
+import com.qreal.wmp.uitesting.dia.scene.window.SceneWindow;
+import com.qreal.wmp.uitesting.dia.scene.window.SceneWindowImpl;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+import com.qreal.wmp.uitesting.pages.Resettable;
+import org.jetbrains.annotations.Contract;
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import static com.codeborne.selenide.Selenide.$;
+
+/**
+ * Describes Scene of Editor.
+ * Can add, rm and manipulate with objects on that area.
+ */
+public class SceneImpl implements Scene, Resettable {
+
+ public static final String SELECTOR = ".scene-wrapper";
+
+ private static final Logger logger = LoggerFactory.getLogger(PaletteImpl.class);
+
+ private final WebDriver webDriver;
+
+ private final SceneWindow sceneWindow;
+
+ private final BlockProvider blockProvider;
+
+ private final LinkProvider linkProvider;
+
+ /** For actions such as mouse move we need driver of current page. */
+ private SceneImpl(WebDriver webDriver) {
+ this.webDriver = webDriver;
+ // For actions such as mouse move we need driver of current page.
+ if (webDriver instanceof JavascriptExecutor) {
+ ((JavascriptExecutor) webDriver).executeScript(
+ createDiv("SceneWindowLeft") + createDiv("SceneWindowTop") +
+ createDiv("SceneWindowHorSize") + createDiv("SceneWindowVerSize")
+ );
+ }
+ sceneWindow = SceneWindowImpl.getSceneWindow(webDriver);
+ blockProvider = BlockProvider.getBlockProvider(sceneWindow, SELECTOR, this);
+ linkProvider = LinkProvider.getLinkProvider(SELECTOR, webDriver);
+ }
+
+ @Override
+ public Block dragAndDrop(final PaletteElement element) {
+ element.getInnerSeleniumELement().dragAndDropTo(SELECTOR);
+ return blockProvider.getNewBlock();
+ }
+
+ @Override
+ public Block dragAndDrop(final PaletteElement element, int cellX, int cellY) {
+ Block newBlock = dragAndDrop(element);
+ blockProvider.moveToCell(newBlock, cellX, cellY);
+ return newBlock;
+ }
+
+ @Override
+ public void moveToCell(Block block, int cellX, int cellY) {
+ blockProvider.moveToCell(block, cellX, cellY);
+ }
+
+ @SuppressWarnings("SimplifiableIfStatement")
+ @Override
+ public boolean exist(SceneElement element) {
+ if (element instanceof Block) {
+ blockProvider.recalculateBlocks();
+ return blockProvider.exist((Block) element);
+ }
+ if (element instanceof Link) {
+ linkProvider.recalculateLinks();
+ return linkProvider.exist((Link) element);
+ }
+ return false;
+ }
+
+ @Override
+ public void remove(SceneElement element) throws ElementNotOnTheSceneException {
+ if (element instanceof Link) {
+ removeSceneElement(((Link) element).getTarget());
+ } else {
+ removeSceneElement(element);
+ }
+ }
+
+ @Override
+ public Link addLink(final Block source, final Block target) {
+ return linkProvider.addLink(source, target);
+ }
+
+ @Override
+ public List getBlocks() {
+ return blockProvider.getBlocks();
+ }
+
+ @Override
+ public void clean() {
+ blockProvider.recalculateBlocks();
+ linkProvider.recalculateLinks();
+ if (!linkProvider.isEmpty()) {
+ try {
+ remove(linkProvider.getLinks().get(0));
+ } catch (ElementNotOnTheSceneException e) {
+ logger.error("It's impossible to remove link, because it is not on the Scene.");
+ }
+ clean();
+ } else {
+ if (!blockProvider.isEmpty()) {
+ try {
+ remove(blockProvider.getBlocks().get(0));
+ } catch (ElementNotOnTheSceneException e) {
+ logger.error("It's impossible to remove block, because it is not on the scene.");
+ }
+ clean();
+ } else {
+ logger.info("Clean scene");
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ blockProvider.recalculateBlocks();
+ linkProvider.recalculateLinks();
+ }
+
+ @Contract("_ -> !null")
+ public static Scene getScene(WebDriver webDriver) {
+ return new SceneImpl(webDriver);
+ }
+
+ @Contract(pure = true)
+ private static String createDiv(String divName) {
+ return "$('body').append('');";
+ }
+
+ @Contract("null -> fail")
+ private void removeSceneElement(SceneElement sceneElement) throws ElementNotOnTheSceneException {
+ sceneWindow.focus(sceneElement.getCoordinateOnScene());
+ logger.info("Remove element {} form scene", sceneElement.getInnerSeleniumElement().toString());
+ new Actions(webDriver)
+ .contextClick(sceneElement.getInnerSeleniumElement())
+ .build()
+ .perform();
+ SelenideElement contextMenu = $(By.id("scene-context-menu"));
+ contextMenu.shouldBe(Condition.visible);
+ contextMenu.click();
+ (new WebDriverWait(webDriver, 5))
+ .until((Predicate) webDriver -> {
+ assert webDriver != null;
+ return webDriver.findElements(sceneElement.getBy()).size() == 0;
+ });
+
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Block.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Block.java
new file mode 100644
index 00000000..0b593b86
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Block.java
@@ -0,0 +1,42 @@
+package com.qreal.wmp.uitesting.dia.scene.elements;
+
+import com.qreal.wmp.uitesting.dia.scene.Scene;
+import org.openqa.selenium.By;
+
+import static com.codeborne.selenide.Selenide.$;
+
+/**
+ * Describes item, which is placed on the scene.
+ * Palette have items. If we dragAndDrop these items to the Scene, we'll get Blocks.
+ */
+public class Block extends SceneElementImpl {
+
+ public static final String CLASS_NAME = "element devs ImageWithPorts";
+
+ private static final String PORT_CLASS_NAME = "port0";
+
+ private final String name;
+
+ private final SceneElement port;
+
+ private final Scene scene;
+
+ public Block(String name, By selector, Scene scene) {
+ super(selector);
+ this.name = name;
+ this.port = new SceneElementImpl(By.id($(selector).find(By.className(PORT_CLASS_NAME)).attr("id")));
+ this.scene = scene;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public SceneElement getPort() {
+ return port;
+ }
+
+ public void moveToCell(int cellX, int cellY) {
+ scene.moveToCell(this, cellX, cellY);
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Link.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Link.java
new file mode 100644
index 00000000..6872a66d
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Link.java
@@ -0,0 +1,52 @@
+package com.qreal.wmp.uitesting.dia.scene.elements;
+
+import com.codeborne.selenide.SelenideElement;
+import com.qreal.wmp.uitesting.dia.scene.Coordinate;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+import org.openqa.selenium.By;
+
+import static com.codeborne.selenide.Selenide.$;
+
+/** Link describes relations between blocks. */
+public class Link extends SceneElementImpl {
+
+ public static final String CLASS_NAME = "link";
+
+ private static final String ARROWHEAD = "marker-arrowheads";
+
+ private final String name;
+
+ private final SceneElement source;
+
+ private final SceneElement target;
+
+ /** Describes link between two blocks. */
+ public Link(String name, By selector) {
+ super(selector);
+ this.name = name;
+ SelenideElement source = $(selector).find(By.className(ARROWHEAD)).find(By.cssSelector(":nth-child(1)"));
+ this.source = new SceneElementImpl(By.id(source.attr("id")));
+ SelenideElement target = $(selector).find(By.className(ARROWHEAD)).find(By.cssSelector(":nth-child(2)"));
+ this.target = new SceneElementImpl(By.id(target.attr("id")));
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public SceneElement getSource() {
+ return source;
+ }
+
+ public SceneElement getTarget() {
+ return target;
+ }
+
+ @Override
+ public Coordinate getCoordinateOnScene() throws ElementNotOnTheSceneException {
+ return new Coordinate(
+ (source.getCoordinateOnScene().getXAbsolute() + target.getCoordinateOnScene().getXAbsolute()) / 2,
+ (source.getCoordinateOnScene().getYAbsolute() + target.getCoordinateOnScene().getYAbsolute()) / 2
+ );
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElement.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElement.java
new file mode 100644
index 00000000..cba5a1cf
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElement.java
@@ -0,0 +1,15 @@
+package com.qreal.wmp.uitesting.dia.scene.elements;
+
+import com.codeborne.selenide.SelenideElement;
+import com.qreal.wmp.uitesting.dia.scene.Coordinate;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+import org.openqa.selenium.By;
+
+/** Describes any element on the Scene. */
+public interface SceneElement {
+ SelenideElement getInnerSeleniumElement();
+
+ By getBy();
+
+ Coordinate getCoordinateOnScene() throws ElementNotOnTheSceneException;
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElementImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElementImpl.java
new file mode 100644
index 00000000..7d0d99d4
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElementImpl.java
@@ -0,0 +1,36 @@
+package com.qreal.wmp.uitesting.dia.scene.elements;
+
+import com.codeborne.selenide.SelenideElement;
+import com.qreal.wmp.uitesting.dia.scene.Coordinate;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+import org.openqa.selenium.By;
+
+import static com.codeborne.selenide.Selenide.$;
+
+/**
+ * All Scene elements have selector by which we can clearly define their web instances.
+ * Also all scene elements have coordinates on the Scene.
+ */
+public class SceneElementImpl implements SceneElement {
+
+ // Wrapper over an string selector. Used to search the element in HTML representation of current page.
+ private final By selector;
+
+ public SceneElementImpl(By selector) {
+ this.selector = selector;
+ }
+
+ /** Based on the Selenium element. */
+ public SelenideElement getInnerSeleniumElement() {
+ return $(selector);
+ }
+
+ public By getBy() {
+ return selector;
+ }
+
+ public Coordinate getCoordinateOnScene() throws ElementNotOnTheSceneException {
+ return Coordinate.getCoordinateFromSeleniumObject(getInnerSeleniumElement())
+ .orElseThrow(ElementNotOnTheSceneException::new);
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/BlockProvider.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/BlockProvider.java
new file mode 100644
index 00000000..c098da99
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/BlockProvider.java
@@ -0,0 +1,93 @@
+package com.qreal.wmp.uitesting.dia.scene.providers;
+
+import com.codeborne.selenide.SelenideElement;
+import com.qreal.wmp.uitesting.dia.scene.Coordinate;
+import com.qreal.wmp.uitesting.dia.scene.Scene;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.window.SceneWindow;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+import org.jetbrains.annotations.Contract;
+import org.openqa.selenium.By;
+import org.openqa.selenium.NotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static com.codeborne.selenide.Selenide.$$;
+
+/** Encapsulates blocks operations. */
+public class BlockProvider {
+
+ private static final Logger logger = LoggerFactory.getLogger(BlockProvider.class);
+
+ private final SceneWindow sceneWindow;
+
+ private final String selector;
+
+ private final Scene scene;
+
+ private Set blocks = new HashSet<>();
+
+ private BlockProvider(SceneWindow sceneWindow, String selector, Scene scene) {
+ this.sceneWindow = sceneWindow;
+ this.selector = selector;
+ this.scene = scene;
+ }
+
+ /** Move element to cell with x and y coordinates. */
+ public void moveToCell(final Block block, final int cellX, final int cellY) {
+ logger.info("Move element {} to cell ({}, {})", block, cellX, cellY);
+ try {
+ sceneWindow.move(block,
+ new Coordinate(cellX * Coordinate.POINT_IN_CELL, cellY * Coordinate.POINT_IN_CELL));
+ } catch (ElementNotOnTheSceneException e) {
+ logger.error("It is impossible to move element, which is not on the Scene");
+ }
+ }
+
+ public List getBlocks() {
+ return Collections.unmodifiableList(blocks.stream().collect(Collectors.toList()));
+ }
+
+ /** Return added block. */
+ public Block getNewBlock() {
+ final SelenideElement newEl = updateBlocks().orElseThrow(NotFoundException::new);
+ logger.info("Add element {} to scene", newEl);
+ Block block = new Block(newEl.attr("id"), By.id(newEl.attr("id")), scene);
+ blocks.add(block);
+ return block;
+ }
+
+ public boolean exist(Block block) {
+ return blocks.stream().anyMatch(anyBlock -> anyBlock.getName().equals(block.getName()));
+ }
+
+ public boolean isEmpty() {
+ return blocks.isEmpty();
+ }
+
+ public void recalculateBlocks() {
+ blocks = $$(By.cssSelector(selector + " #v_7 > *")).stream()
+ .filter(x -> x.attr("class").contains(Block.CLASS_NAME))
+ .map(x -> new Block(x.attr("id"), By.id(x.attr("id")), scene))
+ .collect(Collectors.toSet());
+ }
+
+ @Contract("_, _, _ -> !null")
+ public static BlockProvider getBlockProvider(SceneWindow sceneWindow, String selector, Scene scene) {
+ return new BlockProvider(sceneWindow, selector, scene);
+ }
+
+ /** Return new element of the scene. */
+ private Optional updateBlocks() {
+ final List allElements = $$(By.cssSelector(selector + " #v_7 > *"));
+ return allElements.stream()
+ .filter(htmlElement ->
+ htmlElement.attr("class").contains("element devs ImageWithPorts") &&
+ blocks.stream().noneMatch(block -> block.getInnerSeleniumElement()
+ .attr("id").equals(htmlElement.attr("id")))
+ ).findFirst();
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/LinkProvider.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/LinkProvider.java
new file mode 100644
index 00000000..8e193643
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/LinkProvider.java
@@ -0,0 +1,84 @@
+package com.qreal.wmp.uitesting.dia.scene.providers;
+
+import com.codeborne.selenide.SelenideElement;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.elements.Link;
+import org.jetbrains.annotations.Contract;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.interactions.Actions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
+
+/** Encapsulates links operations. */
+public class LinkProvider {
+
+ private static final Logger logger = LoggerFactory.getLogger(LinkProvider.class);
+
+ private final String selector;
+
+ private final WebDriver webDriver;
+
+ private Set links = new HashSet<>();
+
+ private LinkProvider(String selector, WebDriver webDriver) {
+ this.selector = selector;
+ this.webDriver = webDriver;
+ }
+
+ public List getLinks() {
+ return Collections.unmodifiableList(links.stream().collect(Collectors.toList()));
+ }
+
+ public boolean isEmpty() {
+ return links.isEmpty();
+ }
+
+ public boolean exist(Link link) {
+ return links.stream().anyMatch(anyLink -> anyLink.getName().equals(link.getName()));
+ }
+
+ /** Add link between two blocks. */
+ public Link addLink(final Block source, final Block target) {
+ final SelenideElement begin = $(By.cssSelector(selector + " #" +
+ source.getInnerSeleniumElement().attr("id") + " .outPorts"));
+ logger.info("Begin element {}, end element {} ", begin, target);
+ new Actions(webDriver)
+ .release()
+ .dragAndDrop(source.getPort().getInnerSeleniumElement(), target.getInnerSeleniumElement())
+ .build().perform();
+ SelenideElement newEl = updateLinks().orElseThrow(() -> new NoSuchElementException("Link was not created"));
+ logger.info("Add link {}", newEl);
+ Link res = new Link(newEl.attr("id"), By.id(newEl.attr("id")));
+ links.add(res);
+ return res;
+ }
+
+ public void recalculateLinks() {
+ links = $$(By.cssSelector(selector + " #v_7 > *")).stream()
+ .filter(x -> x.attr("class").contains(Link.CLASS_NAME))
+ .map(x -> new Link(x.attr("id"), By.id(x.attr("id"))))
+ .collect(Collectors.toSet());
+ }
+
+ @Contract("_, _ -> !null")
+ public static LinkProvider getLinkProvider(String selector, WebDriver webDriver) {
+ return new LinkProvider(selector, webDriver);
+ }
+
+ private Optional updateLinks() {
+ final List allElements = $$(By.cssSelector(selector + " #v_7 > *"));
+ return allElements.stream()
+ .filter(htmlElement ->
+ htmlElement.attr("class").contains("link") &&
+ links.stream().noneMatch(link -> htmlElement.attr("id")
+ .equals(link.getInnerSeleniumElement().attr("id")))
+ ).findFirst();
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindow.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindow.java
new file mode 100644
index 00000000..ebe5d8e9
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindow.java
@@ -0,0 +1,23 @@
+package com.qreal.wmp.uitesting.dia.scene.window;
+
+import com.qreal.wmp.uitesting.dia.scene.Coordinate;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+
+public interface SceneWindow {
+
+ /**
+ * Moves element to the requested position.
+ *
+ * @param element element to move
+ * @param dist position to move
+ * */
+ void move(final Block element, final Coordinate dist) throws ElementNotOnTheSceneException;
+
+ /**
+ * Move the screen to requested position.
+ *
+ * @param coordinate coordinate to move
+ */
+ void focus(final Coordinate coordinate);
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindowImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindowImpl.java
new file mode 100644
index 00000000..adee0086
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindowImpl.java
@@ -0,0 +1,187 @@
+package com.qreal.wmp.uitesting.dia.scene.window;
+
+import com.google.common.base.Predicate;
+import com.qreal.wmp.uitesting.dia.scene.Coordinate;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.elements.SceneElement;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+import org.jetbrains.annotations.Contract;
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Random;
+
+import static com.codeborne.selenide.Selenide.$;
+
+/**
+ * Describes part of the scene, which is shown on browser.
+ */
+public class SceneWindowImpl implements SceneWindow {
+
+ private static final Logger logger = LoggerFactory.getLogger(SceneWindowImpl.class);
+
+ private final WebDriver driver;
+
+ /** Constructor takes links to current scene and current driver. */
+ private SceneWindowImpl(final WebDriver driver) {
+ this.driver = driver;
+ }
+
+ /**
+ * Moves element to the requested position.
+ *
+ * @param element element to move
+ * @param dist position to move
+ */
+ @Override
+ public void move(final Block element, final Coordinate dist) throws ElementNotOnTheSceneException {
+ Coordinate src = element.getCoordinateOnScene();
+ focus(src);
+ int sizeHor = Double.valueOf($(By.id("SceneWindowHorSize")).innerHtml()).intValue();
+ int sizeVer = Double.valueOf($(By.id("SceneWindowVerSize")).innerHtml()).intValue();
+
+ Actions actions = new Actions(driver);
+ actions.clickAndHold(element.getInnerSeleniumElement());
+ if (isDistanceLessThenBarrier(
+ dist,
+ element.getCoordinateOnScene(),
+ new OffsetObject(sizeHor / 2, sizeVer / 2))) {
+
+ jump(actions, element, dist);
+ } else {
+ java.util.function.Predicate condX = x ->
+ isDistanceLessThenBarrier(x.getXAbsolute(), dist.getXAbsolute(), sizeHor / 3);
+
+ if (src.getXAbsolute() < dist.getXAbsolute()) {
+ innerMove(actions, element, new OffsetObject(sizeHor / 4, 0), condX);
+ } else {
+ innerMove(actions, element, new OffsetObject(-sizeHor / 4, 0), condX);
+ }
+ }
+ if (Math.abs(dist.getXAbsolute() - element.getCoordinateOnScene().getXAbsolute()) < sizeHor / 2
+ && Math.abs(dist.getYAbsolute() - element.getCoordinateOnScene().getYAbsolute()) < sizeVer / 2) {
+ jump(actions, element, dist);
+ } else {
+ java.util.function.Predicate condY = x ->
+ isDistanceLessThenBarrier(x.getYAbsolute(), dist.getYAbsolute(), sizeVer / 3);
+ if (src.getYAbsolute() < dist.getYAbsolute()) {
+ innerMove(actions, element, new OffsetObject(0, sizeVer / 4), condY);
+ } else {
+ innerMove(actions, element, new OffsetObject(0, -sizeVer / 4), condY);
+ }
+ }
+
+ jump(actions, element, dist);
+
+ (new WebDriverWait(driver, 40))
+ .until((Predicate) webDriver -> {
+ try {
+ return element.getCoordinateOnScene().equals(dist);
+ } catch (ElementNotOnTheSceneException e) {
+ logger.error("It is impossible to move element, which is not on the Scene");
+ }
+ return false;
+ });
+ }
+
+ @Override
+ public void focus(final Coordinate coordinate) {
+ updateCanvasInfo();
+ int sizeHor = Double.valueOf($(By.id("SceneWindowHorSize")).innerHtml()).intValue();
+ int sizeVer = Double.valueOf($(By.id("SceneWindowVerSize")).innerHtml()).intValue();
+
+ logger.info("Focus to " + coordinate.getXAbsolute() + " " + coordinate.getYAbsolute());
+ if (driver instanceof JavascriptExecutor) {
+ ((JavascriptExecutor) driver).executeScript("var canvas = " +
+ "document.getElementsByClassName(\"scene-wrapper\")[0]; " +
+ "var BB=canvas.getBoundingClientRect();" +
+ "canvas.scrollLeft = " + Math.max(0, (coordinate.getXAbsolute() - sizeHor / 2)) + "; " +
+ "canvas.scrollTop = " + Math.max(0, (coordinate.getYAbsolute() - sizeVer / 2)) + ";"
+ );
+ }
+ updateCanvasInfo();
+ }
+
+ @Contract("_ -> !null")
+ public static SceneWindow getSceneWindow(WebDriver webDriver) {
+ return new SceneWindowImpl(webDriver);
+ }
+
+ private void updateCanvasInfo() {
+ if (driver instanceof JavascriptExecutor) {
+ ((JavascriptExecutor) driver).executeScript("var canvas = " +
+ "document.getElementsByClassName(\"scene-wrapper\")[0]; " +
+ "var BB=canvas.getBoundingClientRect();" +
+ "$('#SceneWindowLeft').html(canvas.scrollLeft);" +
+ "$('#SceneWindowTop').html(canvas.scrollTop);" +
+ "$('#SceneWindowHorSize').html(BB.right - BB.left);" +
+ "$('#SceneWindowVerSize').html(BB.bottom - BB.top);"
+ );
+ }
+ }
+
+ private void innerMove(Actions actions, SceneElement element,
+ OffsetObject offset, java.util.function.Predicate cond) {
+
+ (new WebDriverWait(driver, 40))
+ .until((Predicate) webDriver -> {
+ try {
+ Coordinate current = element.getCoordinateOnScene();
+ actions.moveToElement(element.getInnerSeleniumElement()).perform();
+ actions.moveByOffset(offset.offsetX, offset.offsetY).perform();
+
+ focus(element.getCoordinateOnScene());
+ if (element.getCoordinateOnScene().equals(current)) {
+ Random random = new Random();
+ int signX = (int) Math.signum(offset.offsetX);
+ int signY = (int) Math.signum(offset.offsetY);
+ if (offset.offsetX != 0) {
+ offset.offsetX = signX * random.nextInt(Math.abs(offset.offsetX));
+ }
+ if (offset.offsetY != 0) {
+ offset.offsetY = signY * random.nextInt(Math.abs(offset.offsetY));
+ }
+ }
+ return cond.test(element.getCoordinateOnScene());
+ } catch (ElementNotOnTheSceneException e) {
+ logger.error("It is impossible to move element, which is not on the Scene");
+ }
+ return false;
+ });
+ }
+
+ private void jump(Actions actions, SceneElement element, Coordinate dist) throws ElementNotOnTheSceneException {
+ if (!element.getCoordinateOnScene().equals(dist)) {
+ focus(element.getCoordinateOnScene());
+ actions.moveToElement(element.getInnerSeleniumElement()).moveByOffset(
+ dist.getXAbsolute() - element.getCoordinateOnScene().getXAbsolute(),
+ dist.getYAbsolute() - element.getCoordinateOnScene().getYAbsolute()
+ ).release().build().perform();
+ }
+ }
+
+ private class OffsetObject {
+ private int offsetX;
+
+ private int offsetY;
+
+ OffsetObject(int offsetX, int offsetY) {
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ }
+ }
+
+ private boolean isDistanceLessThenBarrier(double fst, double snd, double barrier) {
+ return Math.abs(fst - snd) <= barrier;
+ }
+
+ private boolean isDistanceLessThenBarrier(Coordinate fst, Coordinate snd, OffsetObject barrier) {
+ return isDistanceLessThenBarrier(fst.getXAbsolute(), snd.getXAbsolute(), barrier.offsetX) &&
+ isDistanceLessThenBarrier(fst.getYAbsolute(), snd.getYAbsolute(), barrier.offsetY);
+ }
+}
\ No newline at end of file
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/ElementNotOnTheSceneException.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/ElementNotOnTheSceneException.java
new file mode 100644
index 00000000..53197584
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/ElementNotOnTheSceneException.java
@@ -0,0 +1,9 @@
+package com.qreal.wmp.uitesting.exceptions;
+
+public class ElementNotOnTheSceneException extends Exception {
+
+ public ElementNotOnTheSceneException() {
+ super("It is impossible to get Coordinates of element, which is not on the Scene");
+ }
+
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/WrongAuthException.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/WrongAuthException.java
new file mode 100644
index 00000000..ec2aa1d1
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/WrongAuthException.java
@@ -0,0 +1,9 @@
+package com.qreal.wmp.uitesting.exceptions;
+
+/** Throw if we cannot authorize. */
+public class WrongAuthException extends Exception {
+
+ public WrongAuthException(final String login, final String password) {
+ super("Unable to authorize with login: " + login + " and password: " + password);
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/DiagramStoreService.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/DiagramStoreService.java
new file mode 100644
index 00000000..82a03e4e
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/DiagramStoreService.java
@@ -0,0 +1,120 @@
+package com.qreal.wmp.uitesting.headerpanel;
+
+import com.codeborne.selenide.SelenideElement;
+import com.qreal.wmp.uitesting.dia.scene.SceneImpl;
+import com.qreal.wmp.uitesting.headerpanel.folderwindow.FolderAreaImpl;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.openqa.selenium.By;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Selenide.$;
+
+/**
+ * Keeps custom html representations of saved diagrams.
+ * Also makes final steps of save/open action in folder menu.
+ */
+public class DiagramStoreService {
+
+ private final Map diagrams = new HashMap<>();
+
+ private final By sceneSelector = By.cssSelector(SceneImpl.SELECTOR);
+
+ private String lastKnownKey;
+
+ /** Saves diagram. */
+ public void saveDiagram(String key) {
+ SelenideElement element = $(By.cssSelector(".saving-menu")).find(By.cssSelector(":nth-child(2)"));
+ element.setValue(getFilename(key));
+ diagrams.put(key, prepareElement(Jsoup.parseBodyFragment($(sceneSelector).innerHtml()).body()));
+ $(By.id("saving")).click();
+ lastKnownKey = key;
+ }
+
+ /**
+ * Key is the last knownKey.
+ * Call when user click save button (not SaveAs).
+ */
+ public void saveDiagram() {
+ diagrams.put(lastKnownKey, prepareElement(Jsoup.parseBodyFragment($(sceneSelector).innerHtml()).body()));
+ }
+
+ /** Check if current diagram(now in the scene) equals diagram which is kept in store. */
+ public boolean equalsDrigrams(String key) {
+ return diagrams.get(key).toString().equals(prepareElement(
+ Jsoup.parseBodyFragment($(sceneSelector).innerHtml()).body()).toString()
+ );
+ }
+
+ /** Opens diagram. */
+ public void openDiagram(String key) {
+ String filename = getFilename(key);
+ $(FolderAreaImpl.selector)
+ .findAll(By.className("diagrams"))
+ .stream().filter(elem -> elem.has(text(filename)))
+ .findFirst().ifPresent(SelenideElement::click);
+ }
+
+ public void remove(String key) {
+ diagrams.remove(key);
+ }
+
+ public boolean isDiagramExist(String key) {
+ String filename = getFilename(key);
+ return $(FolderAreaImpl.selector)
+ .findAll(By.className("diagrams"))
+ .stream().anyMatch(elem -> elem.has(text(filename)));
+ }
+
+ /**
+ * Diagrams can be the same but have different generated ids.
+ * It depends on how we restore them by opening.
+ * In this case, we believe that they are the same.
+ * So we get rid of bad ids.
+ */
+ private Element prepareElement(Element element) {
+ removeByAttr(element, "id", el -> el.attr("id").startsWith("j_")
+ || el.attr("id").startsWith("v_")
+ || el.attr("id").startsWith("v-")
+ );
+ removeByAttr(element, "model-id");
+ removeByAttr(element, "data-id");
+ removeByAttr(element, "port", el -> el.attr("port").startsWith("out"));
+ removeByAttr(element, "style", el -> el.attr("style").contains("pointer-events:"));
+ element.getAllElements().stream()
+ .filter(el -> el.className().startsWith("Ports-"))
+ .forEach(el -> el.removeAttr("class"));
+
+ element.getAllElements().stream()
+ .filter(el -> el.className().contains("selected"))
+ .forEach(el -> el.attr("class", el.className().replace(" selected", "")));
+
+ Elements sorted = new Elements(
+ Arrays.stream(element.toString().split("\n"))
+ .sorted()
+ .map(Element::new)
+ .collect(Collectors.toList())
+ );
+ return new Element(sorted.outerHtml());
+ }
+
+ private void removeByAttr(Element element, String attr, Predicate cond) {
+ element.getElementsByAttribute(attr).stream().filter(cond).forEach(el -> el.removeAttr(attr));
+ }
+
+ private void removeByAttr(Element element, String attr) {
+ element.getElementsByAttribute(attr).forEach(el -> el.removeAttr(attr));
+ }
+
+ private String getFilename(String path) {
+ String steps[] = path.split("/");
+ return steps[steps.length - 1];
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/EditorHeaderPanel.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/EditorHeaderPanel.java
new file mode 100644
index 00000000..fc4fc18e
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/EditorHeaderPanel.java
@@ -0,0 +1,23 @@
+package com.qreal.wmp.uitesting.headerpanel;
+
+import com.qreal.wmp.uitesting.headerpanel.folderwindow.FolderArea;
+import com.qreal.wmp.uitesting.pages.DashboardPage;
+
+public interface EditorHeaderPanel {
+
+ DashboardPage toDashboard();
+
+ void newDiagram();
+
+ FolderArea getFolderArea();
+
+ void saveDiagram(String path);
+
+ void saveDiagram();
+
+ void openDiagram(String path);
+
+ boolean isDiagramExist(String path);
+
+ boolean equalsDiagrams(String path);
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/EditorHeaderPanelImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/EditorHeaderPanelImpl.java
new file mode 100644
index 00000000..85df6f58
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/EditorHeaderPanelImpl.java
@@ -0,0 +1,105 @@
+package com.qreal.wmp.uitesting.headerpanel;
+
+import com.qreal.wmp.uitesting.PageFactory;
+import com.qreal.wmp.uitesting.headerpanel.folderwindow.FileItem;
+import com.qreal.wmp.uitesting.headerpanel.folderwindow.FolderArea;
+import com.qreal.wmp.uitesting.pages.DashboardPage;
+import com.qreal.wmp.uitesting.pages.EventProvider;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+
+import static com.codeborne.selenide.Selectors.withText;
+import static com.codeborne.selenide.Selenide.$;
+
+/** {@inheritDoc} */
+public class EditorHeaderPanelImpl implements EditorHeaderPanel {
+
+ public static final By selector = By.id("main-toolbar-area");
+
+ private static final Logger logger = LoggerFactory.getLogger(EditorHeaderPanel.class);
+
+ private final FileItem fileItem;
+
+ private final PageFactory pageFactory;
+
+ private final DiagramStoreService service;
+
+ private final EventProvider eventProvider;
+
+ private EditorHeaderPanelImpl(PageFactory pageFactory, WebDriver webDriver, EventProvider eventProvider) {
+ service = new DiagramStoreService();
+ fileItem = new FileItem(webDriver);
+ this.pageFactory = pageFactory;
+ this.eventProvider = eventProvider;
+ }
+
+ @Override
+ public DashboardPage toDashboard() {
+ $(selector).find(withText("Dashboard")).click();
+ logger.info("Open dashboard");
+ return pageFactory.getDashboardPage();
+ }
+
+ @Override
+ public void newDiagram() {
+ clickFile().newDiagram();
+ eventProvider.resetEvent();
+ logger.info("New diagram");
+ }
+
+ @Override
+ public FolderArea getFolderArea() {
+ return clickFile().getSaveItem();
+ }
+
+ @Override
+ public void saveDiagram(String path) {
+ moveByFolderArea(clickFile().getSaveItem(), path);
+ service.saveDiagram(path);
+ logger.info("Save diagram {}", path);
+ }
+
+ @Override
+ public void saveDiagram() {
+ clickFile().saveDiagram();
+ service.saveDiagram();
+ }
+
+ @Override
+ public void openDiagram(String path) {
+ moveByFolderArea(clickFile().getOpenItem(), path);
+ service.openDiagram(path);
+ logger.info("Open diagram {}", path);
+ }
+
+ @Override
+ public boolean isDiagramExist(String path) {
+ moveByFolderArea(clickFile().getSaveItem(), path);
+ return service.isDiagramExist(path);
+ }
+
+ @Override
+ public boolean equalsDiagrams(String path) {
+ return service.equalsDrigrams(path);
+ }
+
+ public static EditorHeaderPanel getEditorHeaderPanel(PageFactory pageFactory,
+ WebDriver driver,
+ EventProvider eventProvider) {
+ return new EditorHeaderPanelImpl(pageFactory, driver, eventProvider);
+ }
+
+ private FileItem clickFile() {
+ $(FileItem.selector).click();
+ return fileItem;
+ }
+
+ private void moveByFolderArea(FolderArea folderArea, String path) {
+ String[] steps = path.split("/");
+ folderArea.move(String.join("/", Arrays.copyOf(steps, steps.length - 1)));
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FileItem.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FileItem.java
new file mode 100644
index 00000000..12b681b6
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FileItem.java
@@ -0,0 +1,49 @@
+package com.qreal.wmp.uitesting.headerpanel.folderwindow;
+
+import com.codeborne.selenide.Condition;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+import static com.codeborne.selenide.Selectors.withText;
+import static com.codeborne.selenide.Selenide.$;
+
+/** Describes file item on the header menu. */
+public class FileItem {
+
+ public static final By selector = By.id("file-menu");
+
+ private final FolderArea folderArea;
+
+ private final WebDriver driver;
+
+ public FileItem(WebDriver driver) {
+ this.driver = driver;
+ folderArea = new FolderAreaImpl(driver);
+ }
+
+ /** Corresponds 'New' button. */
+ public void newDiagram() {
+ $(selector).find(withText("New")).click();
+ $(SaveDiagramConfirm.selector).waitUntil(Condition.visible, 10000);
+ SaveDiagramConfirm.getSaveDiagramConfirm(driver).notSave();
+ }
+
+ /** Returns folder window by clicking 'SaveAs'. */
+ public FolderArea getSaveItem() {
+ $(selector).find(withText("SaveAs")).click();
+ $(FolderAreaImpl.selector).waitUntil(Condition.visible, 10000);
+ return folderArea;
+ }
+
+ /** Returns folder window by clicking 'Open'. */
+ public FolderArea getOpenItem() {
+ $(selector).find(withText("Open")).click();
+ $(FolderAreaImpl.selector).waitUntil(Condition.visible, 10000);
+ return folderArea;
+ }
+
+ /** Corresponds 'Save' button. */
+ public void saveDiagram() {
+ $(selector).find(withText("Save")).click();
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FolderArea.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FolderArea.java
new file mode 100644
index 00000000..9b7e04e8
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FolderArea.java
@@ -0,0 +1,18 @@
+package com.qreal.wmp.uitesting.headerpanel.folderwindow;
+
+/** Provides interface to working with folder window. */
+public interface FolderArea extends AutoCloseable {
+ FolderArea createFolder(String name);
+
+ boolean isFolderExist(String name);
+
+ FolderArea moveForward(String name);
+
+ FolderArea moveBack();
+
+ String getCurrentPath();
+
+ FolderArea move(String path);
+
+ FolderArea deleteFolder(String name);
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FolderAreaImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FolderAreaImpl.java
new file mode 100644
index 00000000..55ea029f
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FolderAreaImpl.java
@@ -0,0 +1,113 @@
+package com.qreal.wmp.uitesting.headerpanel.folderwindow;
+
+import com.codeborne.selenide.Condition;
+import com.codeborne.selenide.SelenideElement;
+import com.google.common.base.Predicate;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import java.util.Arrays;
+import java.util.function.Function;
+
+import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Selectors.byText;
+import static com.codeborne.selenide.Selenide.$;
+
+/** {@inheritDoc} */
+public class FolderAreaImpl implements FolderArea {
+
+ public static final By selector = By.cssSelector("#diagrams .modal-content");
+
+ private final WebDriver driver;
+
+ public FolderAreaImpl(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ @Override
+ public FolderArea createFolder(String folderName) {
+ $(selector).find(By.id("creating-menu")).click();
+ $(selector).find(By.className("folder-menu")).find(By.cssSelector("[type=\"text\"]")).setValue(folderName);
+ $(selector).find(By.className("folder-menu")).find(By.id("creating")).click();
+ if ($(selector).find(By.className("warning-message")).isDisplayed()) {
+ throw new IllegalArgumentException("The folder with this name already exists");
+ }
+ return this;
+ }
+
+ @Override
+ public boolean isFolderExist(String name) {
+ return $(selector).findAll(By.className("folders")).stream().anyMatch(elem -> elem.has(text(name)));
+ }
+
+ @Override
+ public FolderArea moveForward(String name) {
+ if (!isFolderExist(name)) {
+ throw new IllegalArgumentException("Folder with name " + name + " does not exist");
+ }
+ String oldPath = getCurrentPath();
+ oldPath = "".equals(oldPath) ? name : oldPath + "/" + name;
+ $(selector).find(By.className("folders")).find(byText(name)).click();
+ waitUntilEquals(oldPath, FolderArea::getCurrentPath);
+ return this;
+ }
+
+ @Override
+ public FolderArea moveBack() {
+ String oldPath = getCurrentPath();
+ $(selector).find(By.id("level-up")).click();
+ String[] steps = oldPath.split("/");
+ String diff = String.join("/", Arrays.copyOf(steps, steps.length - 1));
+ waitUntilEquals(diff, FolderArea::getCurrentPath);
+ return this;
+ }
+
+ @Override
+ public String getCurrentPath() {
+ String result = $(selector).find(By.className("folder-path")).find(By.tagName("p")).getText();
+ return result.contains("/") ? result.substring(0, result.length() - 1) : result;
+ }
+
+ @Override
+ public FolderArea move(String path) {
+
+ while (!("").equals(getCurrentPath())) {
+ moveBack();
+ }
+ Arrays.stream(path.split("/")).filter(subfolder -> !subfolder.isEmpty()).forEach(subfolder -> {
+ if (!isFolderExist(subfolder)) {
+ createFolder(subfolder);
+ }
+ moveForward(subfolder);
+ });
+ return this;
+ }
+
+ @Override
+ public FolderArea deleteFolder(String name) {
+ if (!isFolderExist(name)) {
+ throw new IllegalArgumentException("Folder is not exist");
+ }
+ $(selector).find(By.className("folders")).find(byText(name)).contextClick();
+ $(By.id("open-diagram-context-menu")).should(Condition.visible);
+ $(By.id("open-diagram-context-menu")).find(byText("Delete")).click();
+ return this;
+ }
+
+ @Override
+ public void close() {
+ SelenideElement closeButton = $(selector).find(By.className("close"));
+ if ($(selector).isDisplayed() && closeButton.isDisplayed()) {
+ $(selector).find(By.className("close")).click();
+ }
+ }
+
+ private void waitUntilEquals(String path, Function function) {
+ (new WebDriverWait(driver, 10))
+ .until((Predicate) webDriver -> {
+ assert webDriver != null;
+ return path.equals(function.apply(this));
+ });
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/SaveDiagramConfirm.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/SaveDiagramConfirm.java
new file mode 100644
index 00000000..a7ec2ee1
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/SaveDiagramConfirm.java
@@ -0,0 +1,34 @@
+package com.qreal.wmp.uitesting.headerpanel.folderwindow;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+
+import static com.codeborne.selenide.Selectors.withText;
+import static com.codeborne.selenide.Selenide.$;
+
+/** Corresponds window, which appears in case we create new diagram. */
+public class SaveDiagramConfirm {
+
+ private final WebDriver driver;
+
+ public static final By selector = By.id("confirm-save-diagram");
+
+ public SaveDiagramConfirm(WebDriver driver) {
+ this.driver = driver;
+ }
+
+ /** 'No' button. */
+ public void notSave() {
+ $(selector).find(withText("No")).click();
+ }
+
+ /** 'Yes' button. */
+ public FolderArea save() {
+ $(selector).find(withText("Yes")).click();
+ return new FolderAreaImpl(driver);
+ }
+
+ public static SaveDiagramConfirm getSaveDiagramConfirm(WebDriver driver) {
+ return new SaveDiagramConfirm(driver);
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/AuthPage.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/AuthPage.java
new file mode 100644
index 00000000..8159d642
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/AuthPage.java
@@ -0,0 +1,17 @@
+package com.qreal.wmp.uitesting.pages;
+
+/** Describes Authorization page of the WMP project. */
+import static com.codeborne.selenide.Selenide.title;
+
+public class AuthPage {
+
+ private final String title;
+
+ public AuthPage(String title) {
+ this.title = title;
+ }
+
+ public boolean onPage() {
+ return title.equals(title());
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/DashboardPage.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/DashboardPage.java
new file mode 100644
index 00000000..6159c203
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/DashboardPage.java
@@ -0,0 +1,17 @@
+package com.qreal.wmp.uitesting.pages;
+
+import static com.codeborne.selenide.Selenide.title;
+
+/** Describes Dashboard page of the WMP project. */
+public class DashboardPage {
+
+ private final String title;
+
+ public DashboardPage(String title) {
+ this.title = title;
+ }
+
+ public boolean onPage() {
+ return title().equals(title);
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/EditorPage.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/EditorPage.java
new file mode 100644
index 00000000..09b20a62
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/EditorPage.java
@@ -0,0 +1,60 @@
+package com.qreal.wmp.uitesting.pages;
+
+import com.qreal.wmp.uitesting.dia.palette.Palette;
+import com.qreal.wmp.uitesting.dia.property.PropertyEditor;
+import com.qreal.wmp.uitesting.dia.scene.Scene;
+import com.qreal.wmp.uitesting.headerpanel.EditorHeaderPanel;
+
+import static com.codeborne.selenide.Selenide.title;
+
+/**
+ * Describes Editor page of the WMP project.
+ * Includes such components as Scene, Pallete and PropertyEditor.
+ */
+public class EditorPage implements EventProvider.EventListener {
+
+ private final String title;
+
+ private final Scene scene;
+
+ private final Palette palette;
+
+ private final PropertyEditor propertyEditor;
+
+ private final EditorHeaderPanel headerPanel;
+
+ /** Describes page of the Editor and provides components. */
+ public EditorPage(String title, Scene scene, Palette palette, PropertyEditor propertyEditor,
+ EditorHeaderPanel headerPanel) {
+ this.title = title;
+ this.scene = scene;
+ this.palette = palette;
+ this.propertyEditor = propertyEditor;
+ this.headerPanel = headerPanel;
+ }
+
+ public Scene getScene() {
+ return scene;
+ }
+
+ public Palette getPalette() {
+ return palette;
+ }
+
+ public PropertyEditor getPropertyEditor() {
+ return propertyEditor;
+ }
+
+ public EditorHeaderPanel getHeaderPanel() {
+ return headerPanel;
+ }
+
+ public boolean onPage() {
+ return title.equals(title());
+ }
+
+ @Override
+ public void updateEvent() {
+ ((Resettable) scene).reset();
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/EventProvider.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/EventProvider.java
new file mode 100644
index 00000000..75c28af4
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/EventProvider.java
@@ -0,0 +1,31 @@
+package com.qreal.wmp.uitesting.pages;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Catches events and notify observers. */
+public class EventProvider {
+
+ private final List listeners;
+
+ public EventProvider() {
+ listeners = new ArrayList<>();
+ }
+
+ public void addListener(EventListener eventListener) {
+ listeners.add(eventListener);
+ }
+
+ @SuppressWarnings("unused")
+ public void removeListener(EventListener eventListener) {
+ listeners.remove(eventListener);
+ }
+
+ public void resetEvent() {
+ listeners.forEach(EventListener::updateEvent);
+ }
+
+ public interface EventListener {
+ void updateEvent();
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/Resettable.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/Resettable.java
new file mode 100644
index 00000000..31262263
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/Resettable.java
@@ -0,0 +1,5 @@
+package com.qreal.wmp.uitesting.pages;
+
+public interface Resettable {
+ void reset();
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Auther.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Auther.java
new file mode 100644
index 00000000..312f4e36
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Auther.java
@@ -0,0 +1,18 @@
+package com.qreal.wmp.uitesting.services;
+
+import com.qreal.wmp.uitesting.exceptions.WrongAuthException;
+
+/** Used for authentication in current browser session. */
+public interface Auther {
+
+ /**
+ * Implements authentication to the wmp.
+ *
+ * @param username login
+ * @param password password
+ */
+ void auth(final String username, final String password) throws WrongAuthException;
+
+ /** Authentication with fixed login and password. */
+ void auth() throws WrongAuthException;
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Opener.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Opener.java
new file mode 100644
index 00000000..4ae30ae6
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Opener.java
@@ -0,0 +1,21 @@
+package com.qreal.wmp.uitesting.services;
+
+/**
+ * Used for open needed page from wmp in current browser session.
+ * Allows you to access as an authorized user and not.
+ */
+public interface Opener {
+ /**
+ * Opens page from wmp with authentication.
+ *
+ * @param page must be one of the keys from pages.property.
+ */
+ void open(final String page);
+
+ /**
+ * Opens page from wmp without authentication.
+ *
+ * @param page must be one of the keys from pages.property.
+ */
+ void cleanOpen(final String page);
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/AutherImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/AutherImpl.java
new file mode 100644
index 00000000..382c52bf
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/AutherImpl.java
@@ -0,0 +1,41 @@
+package com.qreal.wmp.uitesting.services.impl;
+
+import com.qreal.wmp.uitesting.exceptions.WrongAuthException;
+import com.qreal.wmp.uitesting.services.Auther;
+import org.openqa.selenium.By;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.env.Environment;
+
+import static com.codeborne.selenide.Selectors.byText;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.open;
+
+/** {@inheritDoc} */
+public class AutherImpl implements Auther {
+
+ /** Use properties from pages.properies file. */
+ private Environment env;
+
+ private static final Logger logger = LoggerFactory.getLogger(AutherImpl.class);
+
+ public AutherImpl(Environment env) {
+ this.env = env;
+ }
+
+ /** {@inheritDoc} */
+ public void auth(final String username, final String password) throws WrongAuthException {
+ open(env.getProperty("auth"));
+ $(By.name("username")).setValue(username);
+ $(By.name("password")).setValue(password);
+ $("[type=\"submit\"]").click();
+ if ($(byText("Password or login wrong")).exists()) {
+ throw new WrongAuthException(username, password);
+ }
+ logger.info("Authentication with login: {} and password: {}", username, password);
+ }
+
+ public void auth() throws WrongAuthException {
+ auth("Admin", "Admin");
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/OpenerImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/OpenerImpl.java
new file mode 100644
index 00000000..a5ebf771
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/OpenerImpl.java
@@ -0,0 +1,51 @@
+package com.qreal.wmp.uitesting.services.impl;
+
+import com.qreal.wmp.uitesting.exceptions.WrongAuthException;
+import com.qreal.wmp.uitesting.services.Auther;
+import com.qreal.wmp.uitesting.services.Opener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.env.Environment;
+import org.springframework.security.access.AccessDeniedException;
+
+import static com.codeborne.selenide.Selectors.byText;
+import static com.codeborne.selenide.Selenide.$;
+
+/** {@inheritDoc} */
+public class OpenerImpl implements Opener {
+
+ /** Uses properties from pages.properies file. */
+ private Environment env;
+
+ private Auther auther;
+
+ private static final Logger logger = LoggerFactory.getLogger(OpenerImpl.class);
+
+ public OpenerImpl(Environment env, Auther auther) {
+ this.env = env;
+ this.auther = auther;
+ }
+
+ /** {@inheritDoc} */
+ public void open(final String page) {
+ try {
+ com.codeborne.selenide.Selenide.open(env.getProperty(page));
+ logger.info("Open page {}", env.getProperty(page));
+ if ($(byText("Sign in to continue to Auth")).exists()) {
+ logger.info("Fail with open page {}. Try to login.", env.getProperty(page));
+ auther.auth();
+ }
+ com.codeborne.selenide.Selenide.open(env.getProperty(page));
+ logger.info("Open page {}", env.getProperty(page));
+ } catch (WrongAuthException e) {
+ logger.error("Opener fails: " + e.getMessage());
+ throw new AccessDeniedException(e.getMessage());
+ }
+ logger.info("Open page {}", env.getProperty(page));
+ }
+
+ public void cleanOpen(final String page) {
+ com.codeborne.selenide.Selenide.open(env.getProperty(page));
+ logger.info("Open page {}", env.getProperty(page));
+ }
+}
diff --git a/ui-testing/src/main/resources/pages.properties b/ui-testing/src/main/resources/pages.properties
new file mode 100644
index 00000000..0ffeabe3
--- /dev/null
+++ b/ui-testing/src/main/resources/pages.properties
@@ -0,0 +1,4 @@
+auth=http://localhost:${port.auth}${path.auth}
+dashboard=http://localhost:${port.dashboard}${path.dashboard}
+robotsEditor=http://localhost:${port.editor}${path.editor.robots}
+bpmnEditor=http://localhost:${port.editor}${path.editor.bpmn}
\ No newline at end of file
diff --git a/ui-testing/src/main/resources/services.properties b/ui-testing/src/main/resources/services.properties
deleted file mode 100644
index 1382b546..00000000
--- a/ui-testing/src/main/resources/services.properties
+++ /dev/null
@@ -1 +0,0 @@
-accessDashboardUri=http://localhost:${port.dashboard}${path.dashboard}
\ No newline at end of file
diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/auth/AuthDashboardTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/auth/AuthDashboardTest.java
deleted file mode 100644
index b05f4547..00000000
--- a/ui-testing/src/test/java/com/qreal/wmp/uitesting/auth/AuthDashboardTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.qreal.wmp.uitesting.auth;
-
-import com.codeborne.selenide.WebDriverRunner;
-import io.github.bonigarcia.wdm.ChromeDriverManager;
-import org.apache.commons.lang3.RandomStringUtils;
-import org.junit.*;
-import org.openqa.selenium.By;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.chrome.ChromeDriver;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-
-import static com.codeborne.selenide.Condition.appear;
-import static com.codeborne.selenide.Condition.exist;
-import static com.codeborne.selenide.Selectors.byText;
-import static com.codeborne.selenide.Selenide.$;
-import static com.codeborne.selenide.Selenide.open;
-
-public class AuthDashboardTest {
-
- private static String dashboardUrl;
-
- private static WebDriver driver;
-
- /**
- * Setup ChromeDriverManager and load correct urls from .properties file.
- */
- @BeforeClass
- public static void setUpClass() {
- ChromeDriverManager.getInstance().setup();
- final String resourceName = "services.properties";
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- Properties props = new Properties();
- try (InputStream resourceStream = loader.getResourceAsStream(resourceName)) {
- props.load(resourceStream);
- } catch (IOException e) {
- e.printStackTrace();
- }
- dashboardUrl = props.getProperty("accessDashboardUri");
- }
-
- /**
- * Try to open dashboard page.
- * Should be redirected to auth page.
- */
- @Before
- public void openAuthPage() {
- driver = new ChromeDriver();
- WebDriverRunner.setWebDriver(driver);
- open(dashboardUrl);
- $(byText("Sign in to continue to Auth")).shouldBe(exist);
- $(byText("Dashboard")).shouldNotBe(exist);
- }
-
- /**
- * Try to login with correct username and password.
- * Should access and redirect to dashboard
- */
- @Test
- public void userCanLoginByUsername() {
- $(By.name("username")).setValue("123");
- $(By.name("password")).setValue("123");
- $("[type=\"submit\"]").click();
- $(byText("Dashboard")).waitUntil(appear, 50000);
- }
-
- /**
- * Try to login with random username and password.
- * An error must be shown
- */
- @Test
- public void userWrongAuth() {
- $(byText("Password or login wrong")).shouldNotBe(exist);
- char[] alphabet = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
- String wrongLogin = RandomStringUtils.random(20, alphabet);
- String wrongPassword = RandomStringUtils.random(20, alphabet);
- $(By.name("username")).setValue(wrongLogin);
- $(By.name("password")).setValue(wrongPassword);
- $("[type=\"submit\"]").click();
- $(byText("Password or login wrong")).shouldBe(exist);
- }
-
- @After
- public void logout() {
- driver.close();
- }
-
-}
diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/AuthTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/AuthTest.java
new file mode 100644
index 00000000..5759ade8
--- /dev/null
+++ b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/AuthTest.java
@@ -0,0 +1,124 @@
+package com.qreal.wmp.uitesting.innertests;
+
+import com.qreal.wmp.uitesting.config.AppInit;
+import com.qreal.wmp.uitesting.exceptions.WrongAuthException;
+import com.qreal.wmp.uitesting.services.Auther;
+import com.qreal.wmp.uitesting.services.Opener;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import static com.codeborne.selenide.Condition.appear;
+import static com.codeborne.selenide.Selectors.byText;
+import static com.codeborne.selenide.Selenide.$;
+
+/** Tests for opener and auther services. */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AppInit.class, loader = AnnotationConfigContextLoader.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class AuthTest {
+
+ private static final String WRONG_LOGIN = "lbltfn16vup5boj7o1ju";
+
+ private static final String WRONG_PASSWORD = "8epo7li9uq5vs3wujpm4";
+
+ @Autowired
+ private Auther auther;
+
+ @Autowired
+ private Opener opener;
+
+ /**
+ * Try to login with correct username and password.
+ * Should redirect to OAuth page.
+ */
+ @Test
+ public void authTest() {
+ try {
+ opener.cleanOpen("auth");
+ assert inAuthPage();
+ auther.auth();
+ assert $(byText("OAuth Server")).waitUntil(appear, 5000).exists();
+ } catch (WrongAuthException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+
+ /**
+ * Try to login with random username and password.
+ * An error must be shown.
+ */
+ @Test
+ public void authWrongTest() {
+ opener.cleanOpen("auth");
+ assert inAuthPage();
+ try {
+ auther.auth(WRONG_LOGIN, WRONG_PASSWORD);
+ } catch (WrongAuthException e) {
+ System.err.println(e.getMessage());
+ }
+ $(byText("Password or login wrong")).waitUntil(appear, 5000);
+ }
+
+ /**
+ * Try to open dashboard page without authentication.
+ * Should be redirected to auth page.
+ * Try to open dashboard page with correct login and password.
+ */
+ @Test
+ public void dashboardTest() {
+ opener.cleanOpen("dashboard");
+ assert inAuthPage();
+ opener.open("dashboard");
+ $(byText("Dashboard")).waitUntil(appear, 5000);
+ }
+
+ /**
+ * Try to open robots-editor page without authentication.
+ * Should be redirected to auth page.
+ * Try to open robots-editor page with correct login and password.
+ */
+ @Test
+ public void robotsEditorTest() {
+ opener.cleanOpen("robotsEditor");
+ assert inAuthPage();
+ opener.open("robotsEditor");
+ $(byText("Property Editor")).waitUntil(appear, 5000);
+ }
+
+ /**
+ * Try to open bpmn-editor page without authentication.
+ * Should be redirected to auth page.
+ * Try to open bpmn-editor page with correct login and password.
+ */
+ @Test
+ public void bpmnEditorTest() {
+ opener.cleanOpen("bpmnEditor");
+ assert inAuthPage();
+ opener.open("bpmnEditor");
+ $(byText("Property Editor")).waitUntil(appear, 5000);
+ }
+
+ /**
+ * Check that current page is Auth page.
+ *
+ * @return true if it is
+ */
+ private boolean inAuthPage() {
+ return $(byText("Sign in to continue to Auth")).exists();
+ }
+
+ /** To generate random login and password. */
+ public static void main(String[] args) {
+ final char[] alphabet = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
+ final String wrongLogin = RandomStringUtils.random(20, alphabet);
+ final String wrongPassword = RandomStringUtils.random(20, alphabet);
+ System.out.println(wrongLogin);
+ System.out.println(wrongPassword);
+ }
+}
diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/FolderAreaTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/FolderAreaTest.java
new file mode 100644
index 00000000..f9650d90
--- /dev/null
+++ b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/FolderAreaTest.java
@@ -0,0 +1,148 @@
+package com.qreal.wmp.uitesting.innertests;
+
+import com.qreal.wmp.uitesting.Page;
+import com.qreal.wmp.uitesting.PageLoader;
+import com.qreal.wmp.uitesting.config.AppInit;
+import com.qreal.wmp.uitesting.dia.palette.Palette;
+import com.qreal.wmp.uitesting.dia.scene.Scene;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.elements.Link;
+import com.qreal.wmp.uitesting.headerpanel.EditorHeaderPanel;
+import com.qreal.wmp.uitesting.headerpanel.folderwindow.FolderArea;
+import com.qreal.wmp.uitesting.pages.DashboardPage;
+import com.qreal.wmp.uitesting.pages.EditorPage;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AppInit.class, loader = AnnotationConfigContextLoader.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class FolderAreaTest {
+
+ private static final Logger logger = LoggerFactory.getLogger(FolderAreaTest.class);
+
+ @Autowired
+ private PageLoader pageLoader;
+
+ private Scene scene;
+
+ private Palette pallete;
+
+ private EditorHeaderPanel headerPanel;
+
+ private final char[] alphabet = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
+
+ @Before
+ public void openEditor() {
+ EditorPage editorPage = pageLoader.load(Page.EditorRobots);
+ scene = editorPage.getScene();
+ pallete = editorPage.getPalette();
+ headerPanel = editorPage.getHeaderPanel();
+ }
+
+ @Test
+ public void newDiagramTest() {
+
+ List elements = new ArrayList<>();
+ List links = new ArrayList<>();
+
+ elements.add(scene.dragAndDrop(pallete.getElement("Initial Node"), 4, 4));
+ elements.add(scene.dragAndDrop(pallete.getElement("Motors Forward"), 10, 4));
+ links.add(scene.addLink(elements.get(0), elements.get(1)));
+ elements.add(scene.dragAndDrop(pallete.getElement("Painter Color"), 16, 4));
+ links.add(scene.addLink(elements.get(1), elements.get(2)));
+
+ headerPanel.newDiagram();
+ assert elements.stream().noneMatch(x -> scene.exist(x));
+ assert links.stream().noneMatch(x -> scene.exist(x));
+ }
+
+ @Test
+ public void clickDashboardTest() {
+ DashboardPage dashboardPage = headerPanel.toDashboard();
+ assert dashboardPage.onPage();
+ }
+
+ @Test
+ public void createFolderTest() {
+ final String folder = RandomStringUtils.random(10, alphabet);
+ try (FolderArea folderArea = headerPanel.getFolderArea()) {
+ folderArea.createFolder(folder);
+ assert folderArea.isFolderExist(folder);
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void warnIsFolderExistTest() throws Exception {
+ final String folder = RandomStringUtils.random(10, alphabet);
+ try (FolderArea folderArea = headerPanel.getFolderArea()) {
+ folderArea.createFolder(folder).createFolder(folder);
+ }
+ }
+
+ @Test
+ public void moveForwardFolderTest() {
+ final String folder = RandomStringUtils.random(10, alphabet);
+ try (FolderArea folderArea = headerPanel.getFolderArea()) {
+ Path oldPath = Paths.get("/" + folderArea.getCurrentPath());
+ Path newPath = Paths.get("/" + folderArea.createFolder(folder).moveForward(folder).getCurrentPath());
+ assert newPath.startsWith(oldPath);
+ assert newPath.getFileName().toString().equals(folder);
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+ @Test
+ public void moveBackFolderTest() {
+ final String folder = RandomStringUtils.random(10, alphabet);
+ try (FolderArea folderArea = headerPanel.getFolderArea()) {
+ folderArea.createFolder(folder);
+ Path oldPath = Paths.get("/" + folderArea.moveForward(folder).getCurrentPath());
+ Path newPath = Paths.get("/" + folderArea.moveBack().getCurrentPath());
+ assert oldPath.startsWith(newPath);
+ assert oldPath.getFileName().toString().equals(folder);
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+ @Test
+ public void moveFolderTest() {
+ final String folder = RandomStringUtils.random(10, alphabet) +
+ "/" + RandomStringUtils.random(10, alphabet);
+ try (FolderArea folderArea = headerPanel.getFolderArea()) {
+ folderArea.move(folder);
+ assert folderArea.getCurrentPath().equals(folder);
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+ @Test
+ public void deleteFolderTest() {
+ final String folder = RandomStringUtils.random(10, alphabet);
+ try (FolderArea folderArea = headerPanel.getFolderArea()) {
+ folderArea.createFolder(folder).deleteFolder(folder);
+ assert !folderArea.isFolderExist(folder);
+ } catch (Exception e) {
+ logger.error(e.getMessage());
+ }
+ }
+}
diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/ManipulatingDiagramTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/ManipulatingDiagramTest.java
new file mode 100644
index 00000000..407b2209
--- /dev/null
+++ b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/ManipulatingDiagramTest.java
@@ -0,0 +1,112 @@
+package com.qreal.wmp.uitesting.innertests;
+
+import com.qreal.wmp.uitesting.Page;
+import com.qreal.wmp.uitesting.PageLoader;
+import com.qreal.wmp.uitesting.config.AppInit;
+import com.qreal.wmp.uitesting.dia.palette.Palette;
+import com.qreal.wmp.uitesting.dia.property.PropertyEditor;
+import com.qreal.wmp.uitesting.dia.scene.Scene;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.elements.Link;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+import com.qreal.wmp.uitesting.pages.EditorPage;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AppInit.class, loader = AnnotationConfigContextLoader.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class ManipulatingDiagramTest {
+
+ private static final Logger logger = LoggerFactory.getLogger(ManipulatingDiagramTest.class);
+
+ @Autowired
+ private PageLoader pageLoader;
+
+ private Palette palette;
+
+ private Scene scene;
+
+ private PropertyEditor propertyEditor;
+
+ /** Open editor page. */
+ @Before
+ public void openEditor() {
+ EditorPage editorPage = pageLoader.load(Page.EditorRobots);
+ scene = editorPage.getScene();
+ palette = editorPage.getPalette();
+ propertyEditor = editorPage.getPropertyEditor();
+ }
+
+ /** Drag element from palette and drop on the scene. */
+ @Test
+ public void dragAndDrop() {
+ final Block initialNode = scene.dragAndDrop(palette.getElement("Initial Node"));
+ assert scene.exist(initialNode);
+ }
+
+ /** Remove element from scene. */
+ @Test
+ public void remove() {
+ final Block sceneElement = scene.dragAndDrop(palette.getElement("Initial Node"));
+ assert scene.exist(sceneElement);
+ try {
+ scene.remove(sceneElement);
+ } catch (ElementNotOnTheSceneException e) {
+ logger.error(e.getMessage());
+ }
+ assert !scene.exist(sceneElement);
+ }
+
+ /** Add two elements and link them. */
+ @Test
+ public void addLink() {
+ final Block initNode = scene.dragAndDrop(palette.getElement("Initial Node"), 4, 4);
+ final Block finalNode = scene.dragAndDrop(palette.getElement("Final Node"), 4, 70);
+ final Block motor = scene.dragAndDrop(palette.getElement("Motors Forward"), 4, 7);
+ Link link = scene.addLink(initNode, motor);
+ Link link2 = scene.addLink(motor, finalNode);
+ motor.moveToCell(72, 64);
+ assert scene.exist(link);
+ assert scene.exist(link2);
+ }
+
+ /** Set property 'Ports' of motor forward item to '123' and checks that all is correct. */
+ @Test
+ public void propertyEditor() {
+ final Block motor = scene.dragAndDrop(palette.getElement("Motors Forward"));
+ propertyEditor.setProperty(motor.getInnerSeleniumElement(), "Ports", "123");
+ assert propertyEditor.getProperty(motor.getInnerSeleniumElement(), "Ports").equals("123");
+ }
+
+ /** Move element to cell. */
+ @Test
+ public void moveElement() {
+ final Block motor = scene.dragAndDrop(palette.getElement("Motors Forward"));
+ try {
+ motor.moveToCell(40, 40);
+ assert motor.getCoordinateOnScene().getXCell() == 40 && motor.getCoordinateOnScene().getYCell() == 40;
+ motor.moveToCell(72, 64);
+ assert motor.getCoordinateOnScene().getXCell() == 72 && motor.getCoordinateOnScene().getYCell() == 64;
+ motor.moveToCell(0, 0);
+ assert motor.getCoordinateOnScene().getXCell() == 0 && motor.getCoordinateOnScene().getYCell() == 0;
+ } catch (ElementNotOnTheSceneException e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+ /** Clean scene. */
+ @After
+ public void cleanScene() {
+ scene.clean();
+ }
+}
diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/SaveOpenDiagramTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/SaveOpenDiagramTest.java
new file mode 100644
index 00000000..f3df314e
--- /dev/null
+++ b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/SaveOpenDiagramTest.java
@@ -0,0 +1,106 @@
+package com.qreal.wmp.uitesting.innertests;
+
+import com.qreal.wmp.uitesting.Page;
+import com.qreal.wmp.uitesting.PageLoader;
+import com.qreal.wmp.uitesting.config.AppInit;
+import com.qreal.wmp.uitesting.dia.palette.Palette;
+import com.qreal.wmp.uitesting.dia.scene.Scene;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.elements.Link;
+import com.qreal.wmp.uitesting.headerpanel.EditorHeaderPanel;
+import com.qreal.wmp.uitesting.pages.EditorPage;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AppInit.class, loader = AnnotationConfigContextLoader.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class SaveOpenDiagramTest {
+
+ @Autowired
+ private PageLoader pageLoader;
+
+ private Scene scene;
+
+ private Palette palette;
+
+ private EditorHeaderPanel headerPanel;
+
+ private final char[] alphabet = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
+
+ private final List elements = new ArrayList<>();
+
+ private final List links = new ArrayList<>();
+
+ private String diagram;
+
+ /** Opens editor page and add initial set of elements. */
+ @Before
+ public void openEditor() {
+ EditorPage editorPage = pageLoader.load(Page.EditorRobots);
+ scene = editorPage.getScene();
+ palette = editorPage.getPalette();
+ headerPanel = editorPage.getHeaderPanel();
+ addElements();
+ diagram = RandomStringUtils.random(10, alphabet);
+ headerPanel.saveDiagram(diagram);
+ }
+
+ @Test
+ public void saveDiagramTest() {
+ assert headerPanel.isDiagramExist(diagram);
+ }
+
+ /*
+ @Test
+ public void openDiagramTest() {
+ headerPanel.newDiagram();
+ headerPanel.openDiagram(diagram);
+ assert headerPanel.equalsDiagrams(diagram);
+ }
+ */
+
+ @Test
+ public void equalsTrueTest() {
+ headerPanel.newDiagram();
+ addElements();
+ assert headerPanel.equalsDiagrams(diagram);
+ }
+
+ @Test
+ public void equalsFalseTest() {
+ headerPanel.newDiagram();
+ addElements();
+ scene.moveToCell(elements.get(0), 10, 10);
+ assert !headerPanel.equalsDiagrams(diagram);
+ }
+
+ @Test
+ public void saveAfterChangesTest() {
+ scene.clean();
+ addElements();
+ scene.moveToCell(elements.get(0), 10, 10);
+ headerPanel.saveDiagram();
+ assert headerPanel.equalsDiagrams(diagram);
+ }
+
+ private void addElements() {
+ elements.clear();
+ links.clear();
+ elements.add(scene.dragAndDrop(palette.getElement("Initial Node"), 4, 4));
+ elements.add(scene.dragAndDrop(palette.getElement("Motors Forward"), 10, 4));
+ links.add(scene.addLink(elements.get(0), elements.get(1)));
+ elements.add(scene.dragAndDrop(palette.getElement("Painter Color"), 16, 4));
+ links.add(scene.addLink(elements.get(1), elements.get(2)));
+ }
+}
diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/testspace/DiagramConstructingTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/testspace/DiagramConstructingTest.java
new file mode 100644
index 00000000..cb68505a
--- /dev/null
+++ b/ui-testing/src/test/java/com/qreal/wmp/uitesting/testspace/DiagramConstructingTest.java
@@ -0,0 +1,95 @@
+package com.qreal.wmp.uitesting.testspace;
+
+import com.qreal.wmp.uitesting.Page;
+import com.qreal.wmp.uitesting.PageLoader;
+import com.qreal.wmp.uitesting.config.AppInit;
+import com.qreal.wmp.uitesting.dia.palette.Palette;
+import com.qreal.wmp.uitesting.dia.property.PropertyEditor;
+import com.qreal.wmp.uitesting.dia.scene.Scene;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.elements.Link;
+import com.qreal.wmp.uitesting.pages.EditorPage;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+import java.util.ArrayList;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AppInit.class, loader = AnnotationConfigContextLoader.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class DiagramConstructingTest {
+
+ @Autowired
+ private PageLoader pageLoader;
+
+ private Scene scene;
+
+ private PropertyEditor propertyEditor;
+
+ private ArrayList elements;
+
+ private ArrayList links;
+
+ /** Open editor page. */
+ @Before
+ public void openEditor() {
+ EditorPage editorPage = pageLoader.load(Page.EditorRobots);
+ scene = editorPage.getScene();
+ Palette palette = editorPage.getPalette();
+ propertyEditor = editorPage.getPropertyEditor();
+
+ elements = new ArrayList<>();
+ links = new ArrayList<>();
+
+ elements.add(scene.dragAndDrop(palette.getElement("Initial Node"), 4, 4));
+ elements.add(scene.dragAndDrop(palette.getElement("Motors Forward"), 10, 4));
+ links.add(scene.addLink(elements.get(0), elements.get(1)));
+ elements.add(scene.dragAndDrop(palette.getElement("Painter Color"), 16, 4));
+ links.add(scene.addLink(elements.get(1), elements.get(2)));
+ elements.add(scene.dragAndDrop(palette.getElement("Timer"), 22, 4));
+ links.add(scene.addLink(elements.get(2), elements.get(3)));
+ elements.add(scene.dragAndDrop(palette.getElement("Final Node"), 28, 4));
+ links.add(scene.addLink(elements.get(3), elements.get(4)));
+ }
+
+ @Test
+ public void digramFiveNodes() {
+ assert allExist();
+ }
+
+ @Test
+ public void moveSomeNodes() {
+ elements.get(1).moveToCell(20, 20);
+ elements.get(0).moveToCell(20, 10);
+ elements.get(1).moveToCell(0, 20);
+ assert allExist();
+ }
+
+ @Test
+ public void fillProperties() {
+ propertyEditor.setProperty(elements.get(1).getInnerSeleniumElement(), "Power", "80");
+ assert propertyEditor.getProperty(elements.get(1).getInnerSeleniumElement(), "Power").equals("80");
+ propertyEditor.setProperty(elements.get(2).getInnerSeleniumElement(), "Color", "green");
+ assert propertyEditor.getProperty(elements.get(2).getInnerSeleniumElement(), "Color").equals("green");
+ propertyEditor.setProperty(elements.get(3).getInnerSeleniumElement(), "Delay", "200");
+ assert propertyEditor.getProperty(elements.get(3).getInnerSeleniumElement(), "Delay").equals("200");
+ }
+
+ /** Clean scene. */
+ @After
+ public void cleanScene() {
+ scene.clean();
+ }
+
+ private boolean allExist() {
+ return elements.stream().allMatch(scene::exist) && links.stream().anyMatch(scene::exist);
+ }
+
+}
diff --git a/ui-testing/ui-testing.iml b/ui-testing/ui-testing.iml
index fe03b019..c8edc642 100644
--- a/ui-testing/ui-testing.iml
+++ b/ui-testing/ui-testing.iml
@@ -1,5 +1,14 @@
-
+
+
+
+
+
+ file://$MODULE_DIR$/src/main/java/com/qreal/wmp/uitesting/config/DevConfig.java
+
+
+
+
@@ -12,52 +21,70 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+