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/editor-service/src/main/webapp/app/common/gestures/GesturesMatcher.ts b/editor-service/src/main/webapp/app/common/gestures/GesturesMatcher.ts
index b53bd362..7fbeac8c 100644
--- a/editor-service/src/main/webapp/app/common/gestures/GesturesMatcher.ts
+++ b/editor-service/src/main/webapp/app/common/gestures/GesturesMatcher.ts
@@ -39,7 +39,7 @@ export class GesturesMatcher {
var names: string[] = [];
for (var i = 0; i < this.prevKey; ++i) {
- names[i] = this.gestures[i].name;
+ names[i] = this.gestures[i].name.toLowerCase();
}
return names;
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..307818e1
--- /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.getEditorPageWithGestures();
+ }
+ },
+ 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..19b44078
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageFactory.java
@@ -0,0 +1,79 @@
+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.SceneProxy;
+import com.qreal.wmp.uitesting.headerpanel.EditorHeaderPanelImpl;
+import com.qreal.wmp.uitesting.mousegestures.GestureManipulatorImpl;
+import com.qreal.wmp.uitesting.mousegestures.RobotCalibration;
+import com.qreal.wmp.uitesting.pages.AuthPage;
+import com.qreal.wmp.uitesting.pages.DashboardPage;
+import com.qreal.wmp.uitesting.pages.editor.DefaultEditorPage;
+import com.qreal.wmp.uitesting.pages.editor.EditorPage;
+import com.qreal.wmp.uitesting.pages.editor.EditorPageFacade;
+import com.qreal.wmp.uitesting.pages.editor.EditorPageWithGestures;
+import org.jetbrains.annotations.Contract;
+import org.openqa.selenium.WebDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.codeborne.selenide.WebDriverRunner.url;
+
+/**
+ * Returns page instance for requested uri.
+ * Important: Factory must be used only if url of wanted page is opened in browser.
+ */
+public class PageFactory {
+
+ private static final Logger logger = LoggerFactory.getLogger(PageFactory.class);
+
+ private final WebDriver webDriver;
+
+ public PageFactory(WebDriver webDriver) {
+ this.webDriver = webDriver;
+ }
+
+ /** Returns Editor Page instance. */
+ public EditorPage getEditorPage() {
+ logger.info("Editor page was created");
+ EditorPageFacade editorPageFacade = new EditorPageFacade(url());
+ EditorPage page = getDefaultEditorPage(editorPageFacade);
+ editorPageFacade.setScene((SceneProxy) page.getScene());
+ return page;
+ }
+
+ /** Returns Editor page with gesture decorator. */
+ public EditorPage getEditorPageWithGestures() {
+ EditorPageFacade editorPageFacade = new EditorPageFacade(url());
+ RobotCalibration.calibrate(webDriver);
+ editorPageFacade.reload();
+ EditorPage page = new EditorPageWithGestures(
+ getDefaultEditorPage(editorPageFacade),
+ GestureManipulatorImpl.getGestureManipulator(editorPageFacade)
+ );
+ editorPageFacade.setScene((SceneProxy) page.getScene());
+ return page;
+ }
+
+ /** Returns Dashboard Page instance. */
+ public DashboardPage getDashboardPage() {
+ logger.info("Dashboard page was created");
+ return new DashboardPage();
+ }
+
+ /** Returns Auth Page instance. */
+ public AuthPage getAuthPage() {
+ logger.info("Auth page was created");
+ return new AuthPage();
+ }
+
+ @Contract("_ -> !null")
+ private EditorPage getDefaultEditorPage(EditorPageFacade editorPageFacade) {
+ return new DefaultEditorPage(
+ SceneProxy.getSceneProxy(webDriver, editorPageFacade),
+ PaletteImpl.getPalette(),
+ PropertyEditorImpl.getPropertyEditor(),
+ EditorHeaderPanelImpl.getEditorHeaderPanel(this, webDriver, editorPageFacade)
+ );
+ }
+}
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..051086ba
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/DevConfig.java
@@ -0,0 +1,72 @@
+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.UnexpectedAlertBehaviour;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.remote.CapabilityType;
+import org.openqa.selenium.remote.DesiredCapabilities;
+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();
+ DesiredCapabilities dc = new DesiredCapabilities();
+ dc.setCapability(CapabilityType.UNEXPECTED_ALERT_BEHAVIOUR, UnexpectedAlertBehaviour.ACCEPT);
+ WebDriver driver = new ChromeDriver(dc);
+ 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/DefaultScene.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/DefaultScene.java
new file mode 100644
index 00000000..b8f6fbbe
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/DefaultScene.java
@@ -0,0 +1,161 @@
+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.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.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.List;
+
+import static com.codeborne.selenide.Selenide.$;
+
+/** {@inheritDoc} */
+public class DefaultScene implements Scene {
+
+ private static final Logger logger = LoggerFactory.getLogger(DefaultScene.class);
+
+ private final String selector;
+
+ 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. */
+ public DefaultScene(WebDriver webDriver,
+ String selector,
+ SceneWindow sceneWindow,
+ BlockProvider blockProvider,
+ LinkProvider linkProvider) {
+
+ 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")
+ );
+ }
+ this.sceneWindow = sceneWindow;
+ this.selector = selector;
+ this.blockProvider = blockProvider;
+ this.linkProvider = linkProvider;
+ }
+
+ @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");
+ }
+ }
+ }
+
+ @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/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/SceneProxy.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/SceneProxy.java
new file mode 100644
index 00000000..85c4de8d
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/SceneProxy.java
@@ -0,0 +1,102 @@
+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.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.editor.EditorPageFacade;
+import org.jetbrains.annotations.Contract;
+import org.openqa.selenium.WebDriver;
+
+import java.util.List;
+
+/** Provides Scene interface and getters to its components. */
+public class SceneProxy implements Scene {
+
+ public static final String SELECTOR = ".scene-wrapper";
+
+ private final BlockProvider blockProvider;
+
+ private final LinkProvider linkProvider;
+
+ private final SceneWindow sceneWindow;
+
+ private final Scene scene;
+
+ private SceneProxy(WebDriver driver, EditorPageFacade editorPageFacade) {
+ sceneWindow = SceneWindowImpl.getSceneWindow(driver);
+ blockProvider = BlockProvider.getBlockProvider(sceneWindow, SELECTOR, editorPageFacade);
+ linkProvider = LinkProvider.getLinkProvider(SELECTOR, driver, editorPageFacade);
+ scene = new DefaultScene(driver, SELECTOR, sceneWindow, blockProvider, linkProvider);
+ }
+
+ public BlockProvider getBlockProvider() {
+ return blockProvider;
+ }
+
+ public LinkProvider getLinkProvider() {
+ return linkProvider;
+ }
+
+ public SceneWindow getSceneWindow() {
+ return sceneWindow;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Block dragAndDrop(PaletteElement paletteElement) {
+ return scene.dragAndDrop(paletteElement);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Block dragAndDrop(PaletteElement element, int cellX, int cellY) {
+ return scene.dragAndDrop(element, cellX, cellY);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void moveToCell(Block block, int cellX, int cellY) {
+ scene.moveToCell(block, cellX, cellY);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean exist(SceneElement element) {
+ return scene.exist(element);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void remove(SceneElement element) throws ElementNotOnTheSceneException {
+ scene.remove(element);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Link addLink(Block source, Block target) {
+ return scene.addLink(source, target);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List getBlocks() {
+ return scene.getBlocks();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clean() {
+ scene.clean();
+ }
+
+ @Contract("_, _ -> !null")
+ public static SceneProxy getSceneProxy(WebDriver driver, EditorPageFacade editorPageFacade) {
+ return new SceneProxy(driver, editorPageFacade);
+ }
+}
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..02923d10
--- /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.pages.editor.EditorPageFacade;
+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 EditorPageFacade editorPageFacade;
+
+ public Block(String name, By selector, EditorPageFacade editorPageFacade) {
+ super(selector);
+ this.name = name;
+ this.port = new SceneElementImpl(By.id($(selector).find(By.className(PORT_CLASS_NAME)).attr("id")));
+ this.editorPageFacade = editorPageFacade;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public SceneElement getPort() {
+ return port;
+ }
+
+ public void moveToCell(int cellX, int cellY) {
+ editorPageFacade.move(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..6a95a999
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Link.java
@@ -0,0 +1,57 @@
+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 com.qreal.wmp.uitesting.pages.editor.EditorPageFacade;
+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;
+
+ @SuppressWarnings({"all"})
+ private final EditorPageFacade editorPageFacade;
+
+ /** Describes link between two blocks. */
+ public Link(String name, By selector, EditorPageFacade editorPageFacade) {
+ 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")));
+ this.editorPageFacade = editorPageFacade;
+ }
+
+ 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..68781f46
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/BlockProvider.java
@@ -0,0 +1,97 @@
+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.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.window.SceneWindow;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+import com.qreal.wmp.uitesting.pages.editor.EditorPageFacade;
+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 EditorPageFacade editorPageFacade;
+
+ private Set blocks = new HashSet<>();
+
+ private BlockProvider(SceneWindow sceneWindow, String selector, EditorPageFacade editorPageFacade) {
+ this.sceneWindow = sceneWindow;
+ this.selector = selector;
+ this.editorPageFacade = editorPageFacade;
+ }
+
+ /** 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")), editorPageFacade);
+ 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")), editorPageFacade))
+ .collect(Collectors.toSet());
+ }
+
+ @Contract("_, _, _ -> !null")
+ public static BlockProvider getBlockProvider(
+ SceneWindow sceneWindow,
+ String selector,
+ EditorPageFacade editorPageFacade) {
+
+ return new BlockProvider(sceneWindow, selector, editorPageFacade);
+ }
+
+ /** Return new element of the scene. */
+ public 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..c6facd67
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/LinkProvider.java
@@ -0,0 +1,89 @@
+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 com.qreal.wmp.uitesting.pages.editor.EditorPageFacade;
+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 final EditorPageFacade editorPageFacade;
+
+ private Set links = new HashSet<>();
+
+ private LinkProvider(String selector, WebDriver webDriver, EditorPageFacade editorPageFacade) {
+ this.selector = selector;
+ this.webDriver = webDriver;
+ this.editorPageFacade = editorPageFacade;
+ }
+
+ 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")), editorPageFacade);
+ 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")), editorPageFacade))
+ .collect(Collectors.toSet());
+ }
+
+ @Contract("_, _, _ -> !null")
+ public static LinkProvider getLinkProvider(String selector, WebDriver webDriver, EditorPageFacade facade) {
+ return new LinkProvider(selector, webDriver, facade);
+ }
+
+ /** Returns new link if it was created. */
+ public 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..78bc2e23
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindow.java
@@ -0,0 +1,25 @@
+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);
+
+ void update();
+}
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..ee7ae685
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindowImpl.java
@@ -0,0 +1,188 @@
+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) {
+ update();
+ 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)) + ";"
+ );
+ }
+ update();
+ }
+
+ @Contract("_ -> !null")
+ public static SceneWindow getSceneWindow(WebDriver webDriver) {
+ return new SceneWindowImpl(webDriver);
+ }
+
+ @Override
+ public void update() {
+ 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..6f73405e
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/DiagramStoreService.java
@@ -0,0 +1,122 @@
+package com.qreal.wmp.uitesting.headerpanel;
+
+import com.codeborne.selenide.Condition;
+import com.codeborne.selenide.SelenideElement;
+import com.qreal.wmp.uitesting.dia.scene.SceneProxy;
+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(SceneProxy.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();
+ $(By.id("saving")).shouldBe(Condition.disappear);
+ 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..28a94924
--- /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.editor.EditorPageFacade;
+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 EditorPageFacade editorPageFacade;
+
+ private EditorHeaderPanelImpl(PageFactory pageFactory, WebDriver webDriver, EditorPageFacade editorPageFacade) {
+ service = new DiagramStoreService();
+ fileItem = new FileItem(webDriver);
+ this.pageFactory = pageFactory;
+ this.editorPageFacade = editorPageFacade;
+ }
+
+ @Override
+ public DashboardPage toDashboard() {
+ $(selector).find(withText("Dashboard")).click();
+ logger.info("Open dashboard");
+ return pageFactory.getDashboardPage();
+ }
+
+ @Override
+ public void newDiagram() {
+ clickFile().newDiagram();
+ editorPageFacade.update();
+ 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,
+ EditorPageFacade editorPageFacade) {
+ return new EditorHeaderPanelImpl(pageFactory, driver, editorPageFacade);
+ }
+
+ 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..6c00be6a
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/FolderAreaImpl.java
@@ -0,0 +1,114 @@
+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")).shouldBe(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();
+ $(selector).shouldBe(Condition.disappear);
+ }
+ }
+
+ 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..328ae928
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/headerpanel/folderwindow/SaveDiagramConfirm.java
@@ -0,0 +1,37 @@
+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.$;
+
+/** 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();
+ $(selector).shouldBe(Condition.disappear);
+ }
+
+ /** 'Yes' button. */
+ public FolderArea save() {
+ $(selector).find(withText("Yes")).click();
+ $(selector).shouldBe(Condition.disappear);
+ 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/mousegestures/Gesture.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/Gesture.java
new file mode 100644
index 00000000..132cc7b6
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/Gesture.java
@@ -0,0 +1,37 @@
+package com.qreal.wmp.uitesting.mousegestures;
+
+import java.util.List;
+
+/** Describes gestures for JSON parsing. */
+public class Gesture {
+
+ private String name;
+
+ private List key;
+
+ private double factor;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getKey() {
+ return key;
+ }
+
+ public void setKey(List key) {
+ this.key = key;
+ }
+
+ public double getFactor() {
+ return factor;
+ }
+
+ public void setFactor(double factor) {
+ this.factor = factor;
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/GestureManipulator.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/GestureManipulator.java
new file mode 100644
index 00000000..7041549a
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/GestureManipulator.java
@@ -0,0 +1,22 @@
+package com.qreal.wmp.uitesting.mousegestures;
+
+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.Link;
+import com.qreal.wmp.uitesting.dia.scene.elements.SceneElement;
+
+import java.util.List;
+import java.util.Optional;
+
+/** Provides interface for working with gestures. */
+public interface GestureManipulator {
+
+ /** Draw figure which name is in the parameter. */
+ Block draw(String name);
+
+ /** Draw line between two blocks. */
+ Link drawLine(Block source, Block target);
+
+ /** Draw user's figure. */
+ Optional drawByOffsets(Coordinate start, List offsetsX, List offsetsY);
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/GestureManipulatorImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/GestureManipulatorImpl.java
new file mode 100644
index 00000000..32d0a872
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/GestureManipulatorImpl.java
@@ -0,0 +1,133 @@
+package com.qreal.wmp.uitesting.mousegestures;
+
+import com.codeborne.selenide.SelenideElement;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.qreal.wmp.uitesting.dia.scene.Coordinate;
+import com.qreal.wmp.uitesting.dia.scene.SceneProxy;
+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.pages.editor.EditorPageFacade;
+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.awt.*;
+import java.awt.event.InputEvent;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.codeborne.selenide.Selenide.$;
+
+/** {@inheritDoc} */
+public class GestureManipulatorImpl implements GestureManipulator {
+
+ private static final Logger logger = LoggerFactory.getLogger(GestureManipulator.class);
+
+ private final EditorPageFacade pageFacade;
+
+ private final Painter painter;
+
+ private final Map gestureMap = new HashMap<>();
+
+ private GestureManipulatorImpl(EditorPageFacade pageFacade) {
+ this.pageFacade = pageFacade;
+ painter = new Painter();
+ try (InputStream is = this.getClass().getClassLoader().getResourceAsStream("gestures.json")) {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ Gesture[] gestures = mapper.readValue(is, Gesture[].class);
+ gestureMap.putAll(Arrays.stream(gestures).collect(
+ Collectors.toMap(Gesture::getName, gesture -> gesture)));
+ } catch (IOException e) {
+ logger.error(e.getMessage());
+ }
+ }
+
+ @Override
+ public Block draw(String name) {
+ pageFacade.update();
+ int sizeHor = Double.valueOf($(By.id("SceneWindowHorSize")).innerHtml()).intValue();
+ int sizeVer = Double.valueOf($(By.id("SceneWindowVerSize")).innerHtml()).intValue();
+ Point screenCoordinate = getScreenPosition();
+ painter.paint(
+ gestureMap.get(name).getKey(),
+ new Point(screenCoordinate.x + sizeVer / 3, screenCoordinate.y + sizeHor / 3));
+ return pageFacade.addDrawnBlock(name);
+ }
+
+ @Override
+ public Link drawLine(Block source, Block target) {
+ org.openqa.selenium.Point coordinate = source.getInnerSeleniumElement().getLocation();
+ org.openqa.selenium.Point coordinate2 = target.getInnerSeleniumElement().getLocation();
+ Robot robot;
+ try {
+ robot = new Robot();
+ robot.mouseMove(coordinate.getX() + RobotCalibration.getLastKnownPointX(),
+ coordinate.getY() + RobotCalibration.getLastKnownPointY());
+ robot.mousePress(InputEvent.BUTTON3_DOWN_MASK);
+ robot.mouseMove(coordinate2.getX() + RobotCalibration.getLastKnownPointX(),
+ coordinate2.getY() + RobotCalibration.getLastKnownPointY());
+ robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK);
+ } catch (Exception e) {
+ logger.error("Robot error:" + e.getMessage());
+ }
+ return pageFacade.addDrawnLink();
+ }
+
+ @Override
+ public Optional drawByOffsets(Coordinate start, List offsetsX, List offsetsY) {
+ if (offsetsX.size() != offsetsY.size()) {
+ throw new IllegalArgumentException("Lists of offset must have the same size");
+ }
+ Robot robot;
+ try {
+ robot = new Robot();
+ robot.setAutoWaitForIdle(true);
+ robot.setAutoDelay(300);
+ Point screenPosition = getScreenPosition();
+ int currentX = screenPosition.x + start.getXAbsolute();
+ int currentY = screenPosition.y + start.getYAbsolute();
+ robot.mouseMove(currentX, currentY);
+ robot.mousePress(InputEvent.BUTTON3_DOWN_MASK);
+ robot.mouseMove(currentX - 1, currentY - 1);
+ for (int i = 0; i < offsetsX.size(); ++i) {
+ currentX += offsetsX.get(i);
+ currentY += offsetsY.get(i);
+ robot.mouseMove(currentX, currentY);
+ }
+ robot.mouseMove(currentX - 1, currentY - 1);
+ robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK);
+ robot.delay(100);
+ } catch (Exception e) {
+ logger.error("Robot error:" + e.getMessage());
+ }
+ try {
+ return Optional.of(pageFacade.addDrawnBlock());
+ } catch (NoSuchElementException | NotFoundException ignored) {
+ try {
+ return Optional.of(pageFacade.addDrawnLink());
+ } catch (NoSuchElementException | NotFoundException e) {
+ return Optional.empty();
+ }
+ }
+ }
+
+ @Contract("_ -> !null")
+ public static GestureManipulator getGestureManipulator(EditorPageFacade pageFacade) {
+ return new GestureManipulatorImpl(pageFacade);
+ }
+
+ private Point getScreenPosition() {
+ SelenideElement element = $(By.cssSelector(SceneProxy.SELECTOR));
+ return new Point(
+ element.getLocation().x + RobotCalibration.getLastKnownPointX(),
+ element.getLocation().y + RobotCalibration.getLastKnownPointY());
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/Painter.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/Painter.java
new file mode 100644
index 00000000..631f3d0a
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/Painter.java
@@ -0,0 +1,99 @@
+package com.qreal.wmp.uitesting.mousegestures;
+
+import org.jetbrains.annotations.Contract;
+import org.openqa.selenium.By;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.awt.*;
+import java.awt.event.InputEvent;
+import java.util.List;
+
+import static com.codeborne.selenide.Selenide.$;
+
+/** Class for simulate painting. */
+public class Painter {
+
+ private static final Logger logger = LoggerFactory.getLogger(Painter.class);
+
+ private Point currentPoint;
+
+ private Robot robot;
+
+ private DrawingTable drawingTable;
+
+ /** Right mouse button down and draw gesture (by items from gestures parameter) at the point point. */
+ public void paint(List gestures, Point point) {
+ try {
+ robot = new Robot();
+ robot.setAutoWaitForIdle(true);
+ robot.setAutoDelay(100);
+ drawingTable = new DrawingTable(point);
+ if (gestures.isEmpty()) {
+ return;
+ }
+ String currentCell = gestures.get(0);
+ currentPoint = drawingTable.getPoint(currentCell);
+ robot.mouseMove(currentPoint.x, currentPoint.y);
+ robot.mousePress(InputEvent.BUTTON3_DOWN_MASK);
+ robot.mouseMove(currentPoint.x - 1, currentPoint.y - 1);
+ for (int i = 1; i < gestures.size(); ++ i) {
+ String next = gestures.get(i);
+ if (areNeigbors(currentCell, next)) {
+ drawToCell(next);
+ } else {
+ jumpToCell(next);
+ }
+ currentCell = next;
+ }
+ robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK);
+ } catch (AWTException e) {
+ logger.error("Robot error: " + e.getMessage());
+ }
+ }
+
+ private void drawToCell(String targetCell) {
+ Point targetPoint = drawingTable.getPoint(targetCell);
+ robot.mouseMove(targetPoint.x, targetPoint.y);
+ currentPoint = MouseInfo.getPointerInfo().getLocation();
+ }
+
+ private void jumpToCell(String targetCell) {
+ robot.mouseRelease(InputEvent.BUTTON3_DOWN_MASK);
+ drawToCell(targetCell);
+ robot.mousePress(InputEvent.BUTTON3_DOWN_MASK);
+ robot.mouseMove(currentPoint.x - 1, currentPoint.y - 1);
+ }
+
+ private boolean areNeigbors(String firstCell, String secondCell) {
+ Point diffPoint = diffBetweenCells(firstCell, secondCell);
+ return Math.abs(diffPoint.getX()) <= 1 && Math.abs(diffPoint.getY()) <= 1;
+ }
+
+ @Contract("_, _ -> !null")
+ private Point diffBetweenCells(String firstCell, String secondCell) {
+ return new Point(secondCell.charAt(0) - firstCell.charAt(0), secondCell.charAt(1) - firstCell.charAt(1));
+ }
+
+ private class DrawingTable {
+
+ private final int stepLength;
+
+ private final Point leftUpCorner;
+
+ public DrawingTable(Point leftUpCorner) {
+ this.leftUpCorner = leftUpCorner;
+ int sizeHor = Double.valueOf($(By.id("SceneWindowHorSize")).innerHtml()).intValue();
+ int sizeVer = Double.valueOf($(By.id("SceneWindowVerSize")).innerHtml()).intValue();
+ int minSize = Math.min(sizeHor, sizeVer);
+ stepLength = Math.max(20, minSize / 4 / 6);
+ }
+
+ public Point getPoint(String cell) {
+ int cellOffsetX = (cell.charAt(0) - 'A') * stepLength;
+ int cellOffsetY = (cell.charAt(1) - '0') * stepLength;
+ return new Point(leftUpCorner.x + cellOffsetX, leftUpCorner.y + cellOffsetY);
+ }
+ }
+
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/RobotCalibration.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/RobotCalibration.java
new file mode 100644
index 00000000..0a2814ab
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/mousegestures/RobotCalibration.java
@@ -0,0 +1,172 @@
+package com.qreal.wmp.uitesting.mousegestures;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.awt.*;
+import java.awt.event.InputEvent;
+import java.net.URISyntaxException;
+
+import static com.codeborne.selenide.Selenide.open;
+
+/** For finding top left corner of the body in the any opened html file. */
+public class RobotCalibration {
+
+ private static final Logger logger = LoggerFactory.getLogger(RobotCalibration.class);
+
+ private static int lastKnownPointX;
+
+ private static int lastKnownPointY;
+
+ /** Time for which to wait for the page response. */
+ private static final long TIMEOUT = 1000;
+
+ private final WebDriver driver;
+
+ private final Robot robot;
+
+ private final Point browserCenter;
+
+ private CalibratedPoint xPoint = new CalibratedPoint();
+
+ private CalibratedPoint yPoint = new CalibratedPoint();
+
+ private int counter = 0;
+
+ public static Point calibrate(WebDriver driver) {
+ Point point = new RobotCalibration(driver).calibrate();
+ lastKnownPointX = point.x;
+ lastKnownPointY = point.y;
+ return point;
+ }
+
+ public static int getLastKnownPointX() {
+ return lastKnownPointX;
+ }
+
+ public static int getLastKnownPointY() {
+ return lastKnownPointY;
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ private RobotCalibration(WebDriver driver) {
+ try {
+ open(this.getClass().getClassLoader().getResource("RobotCalibration.html").toURI().toString());
+ } catch (URISyntaxException e) {
+ logger.error(e.getMessage());
+ }
+
+ this.driver = driver;
+ ((JavascriptExecutor) driver).executeScript("alert(\"Focus window\")");
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage());
+ }
+ driver.switchTo().alert().accept();
+ try {
+ driver.manage().window().getSize();
+ } catch (UnsupportedOperationException headlessBrowserException) {
+ throw new IllegalArgumentException("Calibrating a headless browser makes no sense.",
+ headlessBrowserException);
+ }
+
+ try {
+ this.robot = new Robot();
+ } catch (AWTException headlessEnvironmentException) {
+ throw new IllegalStateException("Robot won't work on headless environments.",
+ headlessEnvironmentException);
+ }
+
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ org.openqa.selenium.Dimension browserSize = driver.manage().window().getSize();
+ org.openqa.selenium.Point browserPos = driver.manage().window().getPosition();
+
+ // a maximized browser returns negative position
+ // a maximized browser returns size larger than actual screen size
+ // you can't click outside the screen
+ xPoint.begin = Math.max(0, browserPos.x);
+ xPoint.end = Math.min(xPoint.begin + browserSize.width, screenSize.width - 1);
+ xPoint.mid = (xPoint.begin + xPoint.end) / 2;
+
+ yPoint.begin = Math.max(0, browserPos.y);
+ yPoint.end = Math.min(yPoint.begin + browserSize.height, screenSize.height - 1);
+ yPoint.mid = (yPoint.begin + yPoint.end) / 2;
+
+ browserCenter = new Point(xPoint.mid, yPoint.mid);
+ }
+
+ private Point calibrate() {
+ click(xPoint.begin, yPoint.begin);
+ // find left border
+ while (xPoint.begin < xPoint.end) {
+ click(xPoint.mid, yPoint.mid);
+ xPoint = shift(xPoint);
+ }
+
+ // find top border
+ while (yPoint.begin < yPoint.end) {
+ click(xPoint.mid, yPoint.mid);
+ yPoint = shift(yPoint);
+ }
+
+ if (!isCalibrated()) {
+ throw new IllegalStateException("Couldn't calibrate the Robot.");
+ }
+ return new Point(xPoint.mid + 5, yPoint.mid + 5);
+ }
+
+ /** Clicks on the specified location. */
+ private void click(int xPosition, int yPosition) {
+ robot.mouseMove(xPosition, yPosition);
+ robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
+ robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
+ }
+
+ /** @return whether the click on a page was successful. */
+ private boolean clickWasSuccessful() {
+ counter++;
+
+ long targetTime = System.currentTimeMillis() + TIMEOUT;
+ while (System.currentTimeMillis() < targetTime) {
+ int pageCounter = Integer.parseInt(driver.findElement(By.id("counter")).getAttribute("value"));
+ if (counter == pageCounter) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Returns whether the top left corner has already been clicked at. */
+ private boolean isCalibrated() {
+ long targetTime = System.currentTimeMillis() + TIMEOUT;
+ while (System.currentTimeMillis() < targetTime) {
+ if (driver.findElement(By.id("done")).getAttribute("value").equals("yep")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private CalibratedPoint shift(CalibratedPoint point) {
+ if (clickWasSuccessful()) {
+ point.end = point.mid;
+ } else {
+ point.begin = point.mid + 1;
+ click(browserCenter.x, browserCenter.y);
+ }
+ point.mid = (point.begin + point.end) / 2;
+ return point;
+ }
+
+ private class CalibratedPoint {
+ private int begin;
+
+ private int end;
+
+ private int mid;
+ }
+}
\ No newline at end of file
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/AbstractPage.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/AbstractPage.java
new file mode 100644
index 00000000..7ec908fd
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/AbstractPage.java
@@ -0,0 +1,18 @@
+package com.qreal.wmp.uitesting.pages;
+
+import static com.codeborne.selenide.WebDriverRunner.url;
+
+/** Any page must by identified by its url. */
+public abstract class AbstractPage {
+
+ private final String url;
+
+ protected AbstractPage() {
+ this.url = url();
+ }
+
+ public boolean onPage() {
+ String currentUrl = url();
+ return url.equals(currentUrl.contains("?") ? currentUrl.substring(0, currentUrl.indexOf("?")) : currentUrl);
+ }
+}
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..9423b06b
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/AuthPage.java
@@ -0,0 +1,5 @@
+package com.qreal.wmp.uitesting.pages;
+
+/** Describes Authorization page of the WMP project. */
+public class AuthPage extends AbstractPage {
+}
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..2b48804c
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/DashboardPage.java
@@ -0,0 +1,5 @@
+package com.qreal.wmp.uitesting.pages;
+
+/** Describes Dashboard page of the WMP project. */
+public class DashboardPage extends AbstractPage {
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/DefaultEditorPage.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/DefaultEditorPage.java
new file mode 100644
index 00000000..dca50096
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/DefaultEditorPage.java
@@ -0,0 +1,50 @@
+package com.qreal.wmp.uitesting.pages.editor;
+
+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 com.qreal.wmp.uitesting.pages.AbstractPage;
+
+/** {@inheritDoc} */
+public class DefaultEditorPage extends AbstractPage implements EditorPage {
+
+ 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 DefaultEditorPage(Scene scene, Palette palette, PropertyEditor propertyEditor,
+ EditorHeaderPanel headerPanel) {
+ super();
+ this.scene = scene;
+ this.palette = palette;
+ this.propertyEditor = propertyEditor;
+ this.headerPanel = headerPanel;
+ }
+
+ public Scene getScene() {
+ return scene;
+ }
+
+ @Override
+ public Palette getPalette() {
+ return palette;
+ }
+
+ @Override
+ public PropertyEditor getPropertyEditor() {
+ return propertyEditor;
+ }
+
+ @Override
+ public EditorHeaderPanel getHeaderPanel() {
+ return headerPanel;
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/EditorPage.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/EditorPage.java
new file mode 100644
index 00000000..9a039f41
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/EditorPage.java
@@ -0,0 +1,22 @@
+package com.qreal.wmp.uitesting.pages.editor;
+
+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;
+
+/**
+ * Describes Editor page of the WMP project.
+ * Includes such components as Scene, Pallete and PropertyEditor.
+ */
+public interface EditorPage {
+
+ Scene getScene();
+
+ Palette getPalette();
+
+ PropertyEditor getPropertyEditor();
+
+ EditorHeaderPanel getHeaderPanel();
+
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/EditorPageFacade.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/EditorPageFacade.java
new file mode 100644
index 00000000..7ee05ccf
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/EditorPageFacade.java
@@ -0,0 +1,103 @@
+package com.qreal.wmp.uitesting.pages.editor;
+
+import com.codeborne.selenide.Condition;
+import com.codeborne.selenide.Selenide;
+import com.codeborne.selenide.SelenideElement;
+import com.qreal.wmp.uitesting.dia.palette.Palette;
+import com.qreal.wmp.uitesting.dia.property.PropertyEditor;
+import com.qreal.wmp.uitesting.dia.scene.Coordinate;
+import com.qreal.wmp.uitesting.dia.scene.SceneProxy;
+import com.qreal.wmp.uitesting.dia.scene.elements.Block;
+import com.qreal.wmp.uitesting.dia.scene.elements.Link;
+import org.openqa.selenium.By;
+
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+import static com.codeborne.selenide.Selectors.byText;
+import static com.codeborne.selenide.Selenide.$;
+
+
+/** Facade for components of EditorPage. */
+@SuppressWarnings("unused")
+public class EditorPageFacade {
+
+ private final String url;
+
+ private SceneProxy scene;
+
+ private Palette palette;
+
+ private PropertyEditor propertyEditor;
+
+ public EditorPageFacade(String url, SceneProxy scene, Palette palette, PropertyEditor propertyEditor) {
+ this.url = url;
+ this.scene = scene;
+ this.palette = palette;
+ this.propertyEditor = propertyEditor;
+ }
+
+ public EditorPageFacade(String url) {
+ this.url = url;
+ }
+
+ public void setScene(SceneProxy scene) {
+ this.scene = scene;
+ }
+
+ public void setPallete(Palette pallete) {
+ this.palette = palette;
+ }
+
+ public void setPropertyEditor(PropertyEditor propertyEditor) {
+ this.propertyEditor = propertyEditor;
+ }
+
+ /** Updates all components. */
+ public void update() {
+ scene.getSceneWindow().update();
+ scene.getBlockProvider().recalculateBlocks();
+ scene.getLinkProvider().recalculateLinks();
+ }
+
+ /** Move block source to position (cellX, cellY).*/
+ public void move(Block source, int cellX, int cellY) {
+ scene.moveToCell(source, cellX, cellY);
+ }
+
+ /** Called when new link created by any event in order to let scene known about it. */
+ public Link addDrawnLink() throws NoSuchElementException {
+ Optional newLink = scene.getLinkProvider().updateLinks();
+ if (!newLink.isPresent()) {
+ throw new NoSuchElementException("Link was not created");
+ }
+ scene.getLinkProvider().recalculateLinks();
+ return new Link(newLink.get().attr("id"), By.id(newLink.get().attr("id")), this);
+ }
+
+ /** Called when new block created by any event in order to let scene known about it. */
+ public Block addDrawnBlock(String name) throws NoSuchElementException {
+ if ($(By.className("gestures-menu")).is(Condition.visible)) {
+ $(By.className("gestures-menu")).find(byText(name.toLowerCase())).click();
+ }
+ return scene.getBlockProvider().getNewBlock();
+ }
+
+ /** Called when new block created by any event in order to let scene known about it. */
+ public Block addDrawnBlock() throws NoSuchElementException {
+ if ($(By.className("gestures-menu")).is(Condition.visible)) {
+ $(By.className("gestures-menu")).find(By.tagName("span")).click();
+ }
+ return scene.getBlockProvider().getNewBlock();
+ }
+
+ /** Reload Editor page. */
+ public void reload() {
+ Selenide.open(url);
+ }
+
+ /** Focus to coordinate. */
+ public void focus(Coordinate coordinate) {
+ scene.getSceneWindow().focus(coordinate);
+ }
+}
diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/EditorPageWithGestures.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/EditorPageWithGestures.java
new file mode 100644
index 00000000..bec3a3d5
--- /dev/null
+++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/editor/EditorPageWithGestures.java
@@ -0,0 +1,46 @@
+package com.qreal.wmp.uitesting.pages.editor;
+
+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 com.qreal.wmp.uitesting.mousegestures.GestureManipulator;
+import com.qreal.wmp.uitesting.pages.AbstractPage;
+
+/** {@inheritDoc} */
+public class EditorPageWithGestures extends AbstractPage implements EditorPage {
+
+ private final EditorPage editorPage;
+
+ private final GestureManipulator gestureManipulator;
+
+ public EditorPageWithGestures(EditorPage editorPage, GestureManipulator gestureManipulator) {
+ super();
+ this.editorPage = editorPage;
+ this.gestureManipulator = gestureManipulator;
+ }
+
+ @Override
+ public Scene getScene() {
+ return editorPage.getScene();
+ }
+
+ @Override
+ public Palette getPalette() {
+ return editorPage.getPalette();
+ }
+
+ @Override
+ public PropertyEditor getPropertyEditor() {
+ return editorPage.getPropertyEditor();
+ }
+
+ @Override
+ public EditorHeaderPanel getHeaderPanel() {
+ return editorPage.getHeaderPanel();
+ }
+
+ public GestureManipulator getGestureManipulator() {
+ return gestureManipulator;
+ }
+}
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/RobotCalibration.html b/ui-testing/src/main/resources/RobotCalibration.html
new file mode 100644
index 00000000..af8061f7
--- /dev/null
+++ b/ui-testing/src/main/resources/RobotCalibration.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Calibration Test
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui-testing/src/main/resources/gestures.json b/ui-testing/src/main/resources/gestures.json
new file mode 100644
index 00000000..60442333
--- /dev/null
+++ b/ui-testing/src/main/resources/gestures.json
@@ -0,0 +1,131 @@
+[
+
+ {
+ "name": "FinalNode",
+ "key": [
+ "A0", "B1", "C2", "D3", "E4", "F5", "G6", "H7", "I8",
+ "I0", "H1", "G2", "F3", "E4", "D5", "C6", "B7", "A8"
+ ],
+ "factor": 0.7
+ },
+
+ {
+ "name": "Fork",
+ "key": [
+ "A0", "B0", "C0", "D0", "E0", "F0", "G0", "H0", "I0",
+ "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "I8"
+ ],
+ "factor": 0.7
+ },
+
+ {
+ "name": "Function",
+ "key": [
+ "D0", "E0", "F0", "G0", "H0", "I0", "D0", "D1", "D2", "D3", "D4",
+ "D5", "D6", "D7", "D8", "A4", "B4", "C4", "D4", "E4", "F4"
+ ],
+ "factor": 0.4
+ },
+
+ {
+ "name": "IfBlock",
+ "key": [
+ "E0", "F1", "G2", "H3", "I4", "H5", "G6", "F7",
+ "E8", "D7", "C6", "B5", "A4", "B3", "C2", "D1", "E0"
+ ],
+ "factor": 0.5
+ },
+
+ {
+ "name": "InitialNode",
+ "key": [
+ "C0", "D0", "E0", "F0", "G0", "H1", "I2", "I3", "I4", "I5", "I6", "H7",
+ "G8", "F8", "E8", "D8", "C8", "B7", "A6", "A5", "A4", "A3", "A2", "B1", "C0"
+ ],
+ "factor": 0.3
+ },
+
+ {
+ "name": "Loop",
+ "key": [
+ "A1", "B1", "C1", "D1", "D0", "D2", "E1", "F1",
+ "G1", "H1", "I1", "I2", "I3", "I4", "I5", "I6",
+ "I7", "H7", "G7", "F7", "F6", "F8", "E7", "D7",
+ "C7", "B7", "A7", "A6", "A5", "A4", "A3", "A2"
+ ],
+ "factor": 0.3
+ },
+
+ {
+ "name": "Timer",
+ "key": [
+ "C0", "D0", "E0", "F0", "G0", "H1", "I2", "I3", "I4", "I5", "I6", "H7",
+ "G8", "F8", "E8", "D8", "C8", "B7", "A6", "A5", "A4", "A3", "A2", "B1",
+ "C0", "F0", "F1", "F2", "F3", "F4", "E4", "D4", "C4", "B4", "A4"
+ ],
+ "factor": 0.4
+ },
+
+ {
+ "name": "TrikLed",
+ "key": [
+ "A0", "B0", "C0", "D0", "E0", "F0", "G0", "H0",
+ "I0", "I1", "I2", "I3", "I4", "I5", "A1", "A2",
+ "A3", "A4", "A5", "A6", "B6", "C6", "D6", "E6",
+ "F6", "G6", "H6", "I6", "C7", "C8", "G7", "G8"
+ ],
+ "factor": 0.4
+ },
+
+ {
+ "name": "TrikSmile",
+ "key": [
+ "C0", "C1", "C2", "C3", "G0", "G1", "G2",
+ "G3", "A5", "A6", "B7", "C8", "D8", "E8",
+ "F8", "G8", "H7", "I6", "I5"
+ ],
+ "factor": 0.6
+ },
+
+ {
+ "name": "TrikV62ClearEncoder",
+ "key": [
+ "A0", "B0", "C0", "D0", "E0", "F0", "G0", "H0", "I0",
+ "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8",
+ "B8", "C8", "D8", "E8", "F8", "G8", "H8", "I8",
+ "I0", "I1", "I2", "I3", "I4", "I5", "I6", "I7", "I8",
+ "E2", "E3", "E4", "E5", "E6", "D5", "E6", "F5"
+ ],
+ "factor": 0.3
+ },
+
+ {
+ "name": "TrikV6EnginesBackward",
+ "key": [
+ "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "I4",
+ "A4", "B3", "C2", "D1", "E0", "A4", "B5", "C6", "D7", "E8"
+ ],
+ "factor": 0.5
+ },
+
+ {
+ "name": "TrikV6EnginesForward",
+ "key": [
+ "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "I4",
+ "H3", "G2", "F1", "E0", "I4", "H5", "G6", "F7", "E8"
+ ],
+ "factor": 0.5
+ },
+
+ {
+ "name": "TrikV6EnginesStop",
+ "key": [
+ "C0", "D0", "E0", "F0", "G0", "H1", "I2", "I3",
+ "I4", "I5", "I6", "H7", "G8", "F8", "E8", "D8",
+ "C8", "B7", "A6", "A5", "A4", "A3", "A2", "B1",
+ "C0", "C4", "D4", "E4", "F4", "G4"
+ ],
+ "factor": 0.4
+ }
+
+]
\ No newline at end of file
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..80da515e
--- /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.editor.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/GestureTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/GestureTest.java
new file mode 100644
index 00000000..ede4b877
--- /dev/null
+++ b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/GestureTest.java
@@ -0,0 +1,133 @@
+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.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.elements.Link;
+import com.qreal.wmp.uitesting.dia.scene.elements.SceneElement;
+import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException;
+import com.qreal.wmp.uitesting.mousegestures.GestureManipulator;
+import com.qreal.wmp.uitesting.pages.editor.EditorPageWithGestures;
+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.Arrays;
+import java.util.Collections;
+import java.util.Optional;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AppInit.class, loader = AnnotationConfigContextLoader.class)
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class GestureTest {
+
+ @Autowired
+ private PageLoader pageLoader;
+
+ private Scene scene;
+
+ private Palette palette;
+
+ private GestureManipulator gestureManipulator;
+
+ @Before
+ public void openEditor() {
+ EditorPageWithGestures editorPage = pageLoader.load(Page.EditorRobots);
+ scene = editorPage.getScene();
+ palette = editorPage.getPalette();
+ gestureManipulator = editorPage.getGestureManipulator();
+ }
+
+ @Test
+ public void drawLinkTest() {
+ Block initNode = scene.dragAndDrop(palette.getElement("Initial Node"), 4, 4);
+ Block motorForward = scene.dragAndDrop(palette.getElement("Motors Forward"), 10, 4);
+ assert scene.exist(gestureManipulator.drawLine(initNode, motorForward));
+ }
+
+ @Test
+ public void drawTimer() {
+ assert gestureManipulator.draw("Timer") != null;
+ }
+
+ @Test
+ public void drawFinalNode() {
+ assert gestureManipulator.draw("FinalNode") != null;
+ }
+
+ @Test
+ public void drawTrikV6EnginesStop() {
+ assert gestureManipulator.draw("TrikV6EnginesStop") != null;
+ }
+
+ @Test
+ public void drawTrikV6EnginesForward() {
+ assert gestureManipulator.draw("TrikV6EnginesForward") != null;
+ }
+
+ @Test
+ public void drawTrikV6EnginesBackward() {
+ assert gestureManipulator.draw("TrikV6EnginesBackward") != null;
+ }
+
+ @Test
+ public void drawTrikSmile() {
+ assert gestureManipulator.draw("TrikSmile") != null;
+ }
+
+ @Test
+ public void drawInitialNode() {
+ assert gestureManipulator.draw("InitialNode") != null;
+ }
+
+ @Test
+ public void drawIfBlock() {
+ assert gestureManipulator.draw("IfBlock") != null;
+ }
+
+ @Test
+ public void drawFunction() {
+ assert gestureManipulator.draw("Function") != null;
+ }
+
+ @Test
+ public void randomFigure() {
+ assert !gestureManipulator
+ .drawByOffsets(
+ new Coordinate(100, 100),
+ Arrays.asList(50, 50, -150),
+ Arrays.asList(50, 100, 200))
+ .isPresent();
+ }
+
+ @Test
+ public void customArrow() throws ElementNotOnTheSceneException {
+ Block initNode = scene.dragAndDrop(palette.getElement("Initial Node"), 4, 4);
+ Block motorForward = scene.dragAndDrop(palette.getElement("Motors Forward"), 10, 4);
+ Coordinate initNodeLoc = initNode.getCoordinateOnScene();
+ Coordinate motorNodeLoc = motorForward.getCoordinateOnScene();
+ Optional created = gestureManipulator.drawByOffsets(
+ initNode.getCoordinateOnScene(),
+ Collections.singletonList(motorNodeLoc.getXAbsolute() - initNodeLoc.getXAbsolute()),
+ Collections.singletonList(motorNodeLoc.getYAbsolute() - initNodeLoc.getYAbsolute()));
+ assert created.isPresent() && created.get() instanceof Link;
+ }
+
+ @Test
+ public void customFinalNode() {
+ Optional created = gestureManipulator.drawByOffsets(
+ new Coordinate(100, 100),
+ Arrays.asList(26, 26, 26, 26, 26, 26, 26, 26, -26, -26, -26, -26, 26, 26, 26, 26, -26, -26, -26, -26),
+ Arrays.asList(0, 0, 0, 0, 0, 0, 0, 0, -26, -26, -26, -26, 26, 26, 26, 26, 26, 26, 26, 26));
+ assert created.isPresent() && created.get() instanceof Block;
+ }
+}
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..2cddb196
--- /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.editor.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..2bc5db5a
--- /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.editor.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;
+
+ /** Open editor page and actions before each test. */
+ @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..48ffb454
--- /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.editor.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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+