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/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..f1f9a098 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,9 +22,11 @@ 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 lastCellMouseDownSize: {width: number, height: number}; + private lastCellScrollPosition: {x: number, y: 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(); @@ -170,12 +177,27 @@ export class SceneController { } public createLinkBetweenCurrentAndEventTargetElements(event): void { - var controller = this; - var elementBelow = this.getElementBelow(event, function(cell) { - return !(cell instanceof joint.dia.Link || cell.id === controller.currentElement.getJointObject().id) && controller.rightClickFlag; + var diagramPaper: HTMLDivElement = document.getElementById(this.scene.getId()); + + var elementBelow = this.diagramEditorController.getGraph().get('cells').find((cell) => { + if (cell instanceof joint.dia.Link) return false; // Not interested in links. + if (cell.id === this.currentElement.getJointObject().id) return false; // The same element as the dropped one. + var mXBegin = cell.getBBox().origin().x; + var mYBegin = cell.getBBox().origin().y; + var mXEnd = cell.getBBox().corner().x; + var mYEnd = cell.getBBox().corner().y; + + var leftElementPos:number = (event.pageX - $(diagramPaper).offset().left + $(diagramPaper).scrollLeft()) / + this.scene.getZoom(); + var topElementPos:number = (event.pageY - $(diagramPaper).offset().top + $(diagramPaper).scrollTop()) / + this.scene.getZoom(); + + return ((mXBegin <= leftElementPos) && (mXEnd >= leftElementPos) + && (mYBegin <= topElementPos) && (mYEnd >= topElementPos) && (this.rightClickFlag)) }); + if (elementBelow) { - this.createLink(this.currentElement.getJointObject().id, {id: elementBelow.id}); + this.createLink(this.currentElement.getJointObject().id, elementBelow.id); } } @@ -247,7 +269,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 +292,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 +323,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) { @@ -426,4 +454,102 @@ export class SceneController { && (mYBegin <= topElementPos) && (mYEnd >= topElementPos)); }); } + 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, view): void { + this.scroller.setScroll(true); + var that = this; + switch (this.scroller.getDirection()) { + case Direction.Right: { + this.scroller.setIntervalId(setInterval(() => that.scrollRight(node, event, view), 150)); + break; + } + case Direction.Left: { + this.scroller.setIntervalId(setInterval(() => that.scrollLeft(node, event, view), 150)); + break; + } + case Direction.Down: { + this.scroller.setIntervalId(setInterval(() => that.scrollBottom(node, event, view), 150)); + break; + } + case Direction.Up: { + this.scroller.setIntervalId(setInterval(() => that.scrollTop(node, event, view), 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, view) : 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(), view); + } + } + + private scrollLeft(node: DiagramNode, event, view) : 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(), view); + } + } + + private scrollBottom(node: DiagramNode, event, view) : 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(), view); + } + } + + private scrollTop(node: DiagramNode, event, view) : 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(), view); + } + } + + 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()); + } } diff --git a/editor-core/src/main/webapp/app/core/editorCore/model/DiagramScene.ts b/editor-core/src/main/webapp/app/core/editorCore/model/DiagramScene.ts index 31c36ba4..4bcdb4e7 100644 --- a/editor-core/src/main/webapp/app/core/editorCore/model/DiagramScene.ts +++ b/editor-core/src/main/webapp/app/core/editorCore/model/DiagramScene.ts @@ -4,6 +4,9 @@ import {SubprogramNode} from "./SubprogramNode"; import {DiagramElementListener} from "../controller/DiagramElementListener"; export class DiagramScene extends joint.dia.Paper { + public static get WIDTH(): number {return 2000;} + public static get HEIGHT(): number {return 2000;} + private htmlId: string; private graph: joint.dia.Graph; private currentLinkType: string; @@ -20,8 +23,8 @@ export class DiagramScene extends joint.dia.Paper { super({ el: $('#' + htmlId), - width: 2000, - height: 2000, + width: DiagramScene.WIDTH, + height: DiagramScene.HEIGHT, model: graph, gridSize: gridSize, defaultLink: new joint.dia.Link({ diff --git a/editor-core/src/main/webapp/app/core/editorCore/model/Scroller.ts b/editor-core/src/main/webapp/app/core/editorCore/model/Scroller.ts new file mode 100644 index 00000000..f19bc18a --- /dev/null +++ b/editor-core/src/main/webapp/app/core/editorCore/model/Scroller.ts @@ -0,0 +1,42 @@ +export enum Direction { + Up, Down, Left, Right, None +} + +export class Scroller { + + private scroll: boolean; + + private intervalId: number; + + private direction: Direction; + + constructor() { + this.scroll = false; + this.direction = Direction.None; + } + + public getDirection(): Direction { + return this.direction; + } + + public setDirection(value: Direction) { + this.direction = value; + } + + public getIntervalId(): number { + return this.intervalId; + } + + public setIntervalId(value: number) { + this.intervalId = value; + } + + public getScroll(): boolean { + return this.scroll; + } + + public setScroll(value: boolean) { + this.scroll = value; + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index b8015ce8..7c106d84 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,8 @@ 8080 8082 /editor + /editor/robots + /editor/bpmn /auth /dashboard /editorRest @@ -53,6 +55,8 @@ 8080 8080 /editor + /editor/robots + /editor/bpmn /auth /dashboard /editorRest @@ -70,6 +74,8 @@ 8080 8082 /editor + /editor/robots + /editor/bpmn /auth /dashboard /editorRest diff --git a/ui-testing/pom.xml b/ui-testing/pom.xml index 4853448c..2ca83421 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,55 @@ + + + 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.springframework + spring-beans + ${springframework.version} + + + org.springframework + spring-context + ${springframework.version} + + + org.springframework + spring-webmvc + ${springframework.version} + + + org.springframework + spring-tx + ${springframework.version} + + + org.springframework.security + spring-security-taglibs + ${springframework.security.version} + + + org.springframework + spring-test + ${springframework.version} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/Page.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/Page.java new file mode 100644 index 00000000..834358bd --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/Page.java @@ -0,0 +1,43 @@ +package com.qreal.wmp.uitesting; + +/** Describes WMP pages in browser. */ +@SuppressWarnings("unchecked") +public enum Page { + + Auth("auth") { + @Override + public T getInstance(PageFactory pageFactory) { + return (T) pageFactory.getAuthPage(); + } + }, + Dashboard("dashboard") { + @Override + public T getInstance(PageFactory pageFactory) { + return (T) pageFactory.getDashboardPage(); + } + }, + EditorRobots("robotsEditor") { + @Override + public T getInstance(PageFactory pageFactory) { + return (T) pageFactory.getEditorPage(); + } + }, + EditorBPMN("bpmnEditor") { + @Override + public T getInstance(PageFactory pageFactory) { + return (T) pageFactory.getEditorPage(); + } + }; + + private String name; + + Page(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public abstract T getInstance(PageFactory pageFactory); +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageFactory.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageFactory.java new file mode 100644 index 00000000..fcf2381c --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageFactory.java @@ -0,0 +1,45 @@ +package com.qreal.wmp.uitesting; + +import com.qreal.wmp.uitesting.dia.palette.PaletteImpl; +import com.qreal.wmp.uitesting.dia.property.PropertyEditorImpl; +import com.qreal.wmp.uitesting.dia.scene.SceneImpl; +import com.qreal.wmp.uitesting.pages.AuthPage; +import com.qreal.wmp.uitesting.pages.DashboardPage; +import com.qreal.wmp.uitesting.pages.EditorPage; +import org.openqa.selenium.WebDriver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Returns page instance for requested uri. */ +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"); + return new EditorPage( + SceneImpl.getScene(webDriver), + PaletteImpl.getPalette(), + PropertyEditorImpl.getPropertyEditor() + ); + } + + /** 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(); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageLoader.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageLoader.java new file mode 100644 index 00000000..eb94332e --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/PageLoader.java @@ -0,0 +1,42 @@ +package com.qreal.wmp.uitesting; + +import com.qreal.wmp.uitesting.exceptions.WrongAuthException; +import com.qreal.wmp.uitesting.services.Auther; +import com.qreal.wmp.uitesting.services.Opener; + +/** + * Loads page. + * It means, firstly, it opens uri by Opener service. + * Secondly, it returns page by PageFactory. + */ +public class PageLoader { + + private final PageFactory pageFactory; + + private final Opener opener; + + private final Auther auther; + + public PageLoader(PageFactory pageFactory, Opener opener, Auther auther) { + this.pageFactory = pageFactory; + this.opener = opener; + this.auther = auther; + } + + /** Loads and returns requested page with default authentication. */ + public T load(Page page) { + opener.open(page.getName()); + return getPage(page); + } + + /** Loads and returns requested page with login and password. */ + public T load(Page page, String username, String password) throws WrongAuthException { + auther.auth(username, password); + opener.open(page.getName()); + return getPage(page); + } + + private T getPage(Page page) { + return page.getInstance(pageFactory); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/AppInit.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/AppInit.java new file mode 100644 index 00000000..179b4bda --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/AppInit.java @@ -0,0 +1,18 @@ +package com.qreal.wmp.uitesting.config; + +import io.github.bonigarcia.wdm.ChromeDriverManager; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; + +@ComponentScan("com.qreal.wmp.uitesting") +public class AppInit { + + /** Main function creates context. */ + public static void main(final String... args) { + ChromeDriverManager.getInstance().setup(); + final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.scan("com.qreal.wmp.uitesting"); + context.register(AppInit.class); + context.refresh(); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/DevConfig.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/DevConfig.java new file mode 100644 index 00000000..5632aa9a --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/config/DevConfig.java @@ -0,0 +1,67 @@ +package com.qreal.wmp.uitesting.config; + +import com.codeborne.selenide.WebDriverRunner; +import com.qreal.wmp.uitesting.PageFactory; +import com.qreal.wmp.uitesting.PageLoader; +import com.qreal.wmp.uitesting.services.Auther; +import com.qreal.wmp.uitesting.services.Opener; +import com.qreal.wmp.uitesting.services.impl.AutherImpl; +import com.qreal.wmp.uitesting.services.impl.OpenerImpl; +import io.github.bonigarcia.wdm.ChromeDriverManager; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.chrome.ChromeDriver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.env.Environment; + +import java.util.concurrent.TimeUnit; + +/** Creates beans for Spring needs. **/ +@Configuration +@PropertySource("classpath:pages.properties") +public class DevConfig { + + @Autowired + private Environment environment; + + /** Processor for Environment linked to property files.*/ + @Bean + public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean + public WebDriver webDriver() { + ChromeDriverManager.getInstance().setup(); + WebDriver driver = new ChromeDriver(); + driver.manage().window().maximize(); + driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS); + driver.manage().timeouts().setScriptTimeout(3, TimeUnit.SECONDS); + driver.manage().timeouts().pageLoadTimeout(20, TimeUnit.SECONDS); + WebDriverRunner.setWebDriver(driver); + return driver; + } + + @Bean + public Auther auther() { + return new AutherImpl(environment); + } + + @Bean + public Opener opener() { + return new OpenerImpl(environment, auther()); + } + + @Bean + public PageFactory pageFactory() { + return new PageFactory(webDriver()); + } + + @Bean + public PageLoader pageLoader() { + return new PageLoader(pageFactory(), opener(), auther()); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/Palette.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/Palette.java new file mode 100644 index 00000000..111c0f43 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/Palette.java @@ -0,0 +1,18 @@ +package com.qreal.wmp.uitesting.dia.palette; + +import org.openqa.selenium.NoSuchElementException; + +/** + * Describes Palette. + * For any manipulating with it. + */ +public interface Palette { + + /** + * Chooses an element from Palette. + * + * @param elementName name of block + * @return block + */ + PaletteElement getElement(String elementName) throws NoSuchElementException; +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteElement.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteElement.java new file mode 100644 index 00000000..6ae9e640 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteElement.java @@ -0,0 +1,25 @@ +package com.qreal.wmp.uitesting.dia.palette; + +import com.codeborne.selenide.SelenideElement; + + +/** Describes palette's items. */ +public class PaletteElement { + + private final SelenideElement innerSeleniumELement; + + private final String name; + + public PaletteElement(SelenideElement innerSeleniumELement) { + this.innerSeleniumELement = innerSeleniumELement; + name = innerSeleniumELement.attr("data-type"); + } + + public SelenideElement getInnerSeleniumELement() { + return innerSeleniumELement; + } + + public String getName() { + return name; + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteImpl.java new file mode 100644 index 00000000..8f3d1ef3 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/palette/PaletteImpl.java @@ -0,0 +1,28 @@ +package com.qreal.wmp.uitesting.dia.palette; + +import com.codeborne.selenide.SelenideElement; +import org.openqa.selenium.By; +import org.openqa.selenium.NoSuchElementException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.codeborne.selenide.Selectors.withText; +import static com.codeborne.selenide.Selenide.$; + +/** {@inheritDoc} */ +public class PaletteImpl implements Palette { + + private static final String SELECTOR = "#palette-tab-content"; + + private static final Logger logger = LoggerFactory.getLogger(PaletteImpl.class); + + public PaletteElement getElement(final String elementName) throws NoSuchElementException { + final SelenideElement element = $(By.cssSelector(SELECTOR)).find(withText(elementName)); + logger.info("Get element {} from Palette", element); + return new PaletteElement(element); + } + + public static Palette getPalette() { + return new PaletteImpl(); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditor.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditor.java new file mode 100644 index 00000000..1d96ef40 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditor.java @@ -0,0 +1,16 @@ +package com.qreal.wmp.uitesting.dia.property; + +import com.codeborne.selenide.SelenideElement; +import org.openqa.selenium.NoSuchElementException; + +/** + * Describe Property Editor. + * When block is clicked, it could be configured by Property Editor. + */ +public interface PropertyEditor { + /** Set property of element which on the focus. */ + void setProperty(SelenideElement element, String propertyName, String propertyValue) throws NoSuchElementException; + + /** Return the value of property by name. */ + String getProperty(final SelenideElement element, final String propertyName) throws NoSuchElementException; +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditorImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditorImpl.java new file mode 100644 index 00000000..1200616e --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/property/PropertyEditorImpl.java @@ -0,0 +1,67 @@ +package com.qreal.wmp.uitesting.dia.property; + +import com.codeborne.selenide.SelenideElement; +import org.openqa.selenium.By; +import org.openqa.selenium.NoSuchElementException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.OptionalInt; +import java.util.stream.IntStream; + +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +/** {@inheritDoc} */ +public class PropertyEditorImpl implements PropertyEditor { + + private static final String SELECTOR = "#property_table"; + + private static final Logger logger = LoggerFactory.getLogger(PropertyEditorImpl.class); + + /** {@inheritDoc} */ + public void setProperty(final SelenideElement element, final String propertyName, final String propertyValue) + throws NoSuchElementException + { + element.click(); + SelenideElement property = getInputOfElement(propertyName); + if (property.attr("class").equals("input-group")) { + property.find(By.xpath(".//*")).setValue(propertyValue); + } else { + property.selectOptionByValue(propertyValue); + } + logger.info("Set property {} to {}", propertyName, propertyValue); + } + + /** {@inheritDoc} */ + public String getProperty(final SelenideElement element, final String propertyName) throws NoSuchElementException { + $(By.cssSelector(SELECTOR)).click(); + element.click(); + SelenideElement property = getInputOfElement(propertyName); + logger.info("Get value of preperty {}", propertyName); + if (property.attr("class").equals("input-group")) { + return property.find(By.xpath(".//*")).getValue(); + } else { + return property.getSelectedOption().getValue(); + } + } + + public static PropertyEditor getPropertyEditor() { + return new PropertyEditorImpl(); + } + + /** To set/get property we need to take web element which describes needed field. */ + private SelenideElement getInputOfElement(final String propertyName) { + final List allChilds = $$(By.cssSelector(SELECTOR + " tbody > * > *")); + final OptionalInt indexOfNeeded = IntStream.range(0, allChilds.size()) + .filter(index -> allChilds.get(index).getText().contains(propertyName)) + .findFirst(); + if (!indexOfNeeded.isPresent()) { + throw new NoSuchElementException("There is no property with name " + propertyName); + } + return $(By.cssSelector(SELECTOR + " tbody > *:nth-of-type(" + + (indexOfNeeded.getAsInt() / 2 + 1) + + ") > *:nth-of-type(2) > *" )); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Coordinate.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Coordinate.java new file mode 100644 index 00000000..d6d08559 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Coordinate.java @@ -0,0 +1,64 @@ +package com.qreal.wmp.uitesting.dia.scene; + +import com.codeborne.selenide.SelenideElement; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * Describes element's position on the Scene. + * Contains absolute coordinates of scene, which are written in 'transform' tag on the html representation. + * Also contains cell's position (the Scene is represented by a mesh of cells) + */ +public class Coordinate { + + public static final String SELECTOR = "transform"; + + public static final int POINT_IN_CELL = 25; + + private final int xAbsolute; + + private final int yAbsolute; + + /** Returns coordinate of object on scene. */ + @NotNull + public static Optional getCoordinateFromSeleniumObject(SelenideElement element) { + final String position = element.attr(SELECTOR); + final String[] pairStr = position.substring(position.indexOf('(') + 1, position.indexOf(')')).split(","); + return Optional.of( + new Coordinate( + Double.valueOf(pairStr[0]).intValue(), + Double.valueOf(pairStr[1]).intValue() + ) + ); + } + + public Coordinate(int xAbsolute, int yAbsolute) { + this.xAbsolute = xAbsolute; + this.yAbsolute = yAbsolute; + } + + public int getXCell() { + return xAbsolute / POINT_IN_CELL; + } + + public int getYCell() { + return yAbsolute / POINT_IN_CELL; + } + + public int getXAbsolute() { + return xAbsolute; + } + + public int getYAbsolute() { + return yAbsolute; + } + + public boolean equals(final Coordinate other) { + return xAbsolute == other.getXAbsolute() && yAbsolute == other.getYAbsolute(); + } + + public String toString() { + return "(" + getXAbsolute() + "," + getYAbsolute() + ")"; + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Scene.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Scene.java new file mode 100644 index 00000000..778dfe85 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/Scene.java @@ -0,0 +1,35 @@ +package com.qreal.wmp.uitesting.dia.scene; + +import com.qreal.wmp.uitesting.dia.palette.PaletteElement; +import com.qreal.wmp.uitesting.dia.scene.elements.Block; +import com.qreal.wmp.uitesting.dia.scene.elements.Link; +import com.qreal.wmp.uitesting.dia.scene.elements.SceneElement; +import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException; + +import java.util.List; + +public interface Scene { + /** Drag element from scene or palette and put it on the center of scene. */ + Block dragAndDrop(PaletteElement paletteElement); + + /** Drag element from scene or palette and put it in cell of the scene. */ + Block dragAndDrop(PaletteElement element, int cellX, int cellY); + + /** Move element from scene to the cell. */ + void moveToCell(Block block, int cellX, int cellY); + + /** Check if element exist on the scene. */ + boolean exist(SceneElement element); + + /** Remove block from the scene. */ + void remove(SceneElement element) throws ElementNotOnTheSceneException; + + /** Add link between two elements. */ + Link addLink(Block source, Block target); + + /** Return all blocks. */ + List getBlocks(); + + /** Remove all elements from the scene. */ + void clean(); +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/SceneImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/SceneImpl.java new file mode 100644 index 00000000..e8361775 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/SceneImpl.java @@ -0,0 +1,162 @@ +package com.qreal.wmp.uitesting.dia.scene; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import com.google.common.base.Predicate; +import com.qreal.wmp.uitesting.dia.palette.PaletteElement; +import com.qreal.wmp.uitesting.dia.palette.PaletteImpl; +import com.qreal.wmp.uitesting.dia.scene.elements.Block; +import com.qreal.wmp.uitesting.dia.scene.elements.Link; +import com.qreal.wmp.uitesting.dia.scene.elements.SceneElement; +import com.qreal.wmp.uitesting.dia.scene.providers.BlockProvider; +import com.qreal.wmp.uitesting.dia.scene.providers.LinkProvider; +import com.qreal.wmp.uitesting.dia.scene.window.SceneWindow; +import com.qreal.wmp.uitesting.dia.scene.window.SceneWindowImpl; +import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException; +import org.jetbrains.annotations.Contract; +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static com.codeborne.selenide.Selenide.$; + +/** + * Describes Scene of Editor. + * Can add, rm and manipulate with objects on that area. + */ +public class SceneImpl implements Scene { + + private static final String SELECTOR = ".scene-wrapper"; + + private static final Logger logger = LoggerFactory.getLogger(PaletteImpl.class); + + private final WebDriver webDriver; + + private final SceneWindow sceneWindow; + + private final BlockProvider blockProvider; + + private final LinkProvider linkProvider; + + /** For actions such as mouse move we need driver of current page. */ + private SceneImpl(WebDriver webDriver) { + this.webDriver = webDriver; + if (webDriver instanceof JavascriptExecutor) { + ((JavascriptExecutor) webDriver).executeScript( + createDiv("SceneWindowLeft") + createDiv("SceneWindowTop") + + createDiv("SceneWindowHorSize") + createDiv("SceneWindowVerSize") + ); + } + sceneWindow = SceneWindowImpl.getSceneWindow(webDriver); + blockProvider = BlockProvider.getBlockProvider(sceneWindow, SELECTOR, this); + linkProvider = LinkProvider.getLinkProvider(SELECTOR, webDriver); + } + + @Override + public Block dragAndDrop(final PaletteElement element) { + element.getInnerSeleniumELement().dragAndDropTo(SELECTOR); + return blockProvider.getNewBlock(); + } + + @Override + public Block dragAndDrop(final PaletteElement element, int cellX, int cellY) { + Block newBlock = dragAndDrop(element); + blockProvider.moveToCell(newBlock, cellX, cellY); + return newBlock; + } + + @Override + public void moveToCell(Block block, int cellX, int cellY) { + blockProvider.moveToCell(block, cellX, cellY); + } + + @SuppressWarnings("SimplifiableIfStatement") + @Override + public boolean exist(SceneElement element) { + if (element instanceof Block) { + return blockProvider.exist((Block) element); + } + if (element instanceof Link) { + 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() { + 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("_ -> !null") + public static Scene getScene(WebDriver webDriver) { + return new SceneImpl(webDriver); + } + + @Contract(pure = true) + private static String createDiv(String divName) { + return "$('body').append('');"; + } + + @Contract("null -> fail") + private void removeSceneElement(SceneElement sceneElement) throws ElementNotOnTheSceneException { + sceneWindow.focus(sceneElement.getCoordinateOnScene()); + logger.info("Remove element {} form scene", sceneElement.getInnerSeleniumElement().toString()); + new Actions(webDriver) + .contextClick(sceneElement.getInnerSeleniumElement()) + .build() + .perform(); + SelenideElement contextMenu = $(By.id("scene-context-menu")); + contextMenu.shouldBe(Condition.visible); + contextMenu.click(); + (new WebDriverWait(webDriver, 5)) + .until((Predicate) webDriver -> { + assert webDriver != null; + return webDriver.findElements(sceneElement.getBy()).size() == 0; + }); + blockProvider.recalculateBlocks(); + linkProvider.recalculateLinks(); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Block.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Block.java new file mode 100644 index 00000000..0b593b86 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Block.java @@ -0,0 +1,42 @@ +package com.qreal.wmp.uitesting.dia.scene.elements; + +import com.qreal.wmp.uitesting.dia.scene.Scene; +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Selenide.$; + +/** + * Describes item, which is placed on the scene. + * Palette have items. If we dragAndDrop these items to the Scene, we'll get Blocks. + */ +public class Block extends SceneElementImpl { + + public static final String CLASS_NAME = "element devs ImageWithPorts"; + + private static final String PORT_CLASS_NAME = "port0"; + + private final String name; + + private final SceneElement port; + + private final Scene scene; + + public Block(String name, By selector, Scene scene) { + super(selector); + this.name = name; + this.port = new SceneElementImpl(By.id($(selector).find(By.className(PORT_CLASS_NAME)).attr("id"))); + this.scene = scene; + } + + public String getName() { + return name; + } + + public SceneElement getPort() { + return port; + } + + public void moveToCell(int cellX, int cellY) { + scene.moveToCell(this, cellX, cellY); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Link.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Link.java new file mode 100644 index 00000000..6872a66d --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/Link.java @@ -0,0 +1,52 @@ +package com.qreal.wmp.uitesting.dia.scene.elements; + +import com.codeborne.selenide.SelenideElement; +import com.qreal.wmp.uitesting.dia.scene.Coordinate; +import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException; +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Selenide.$; + +/** Link describes relations between blocks. */ +public class Link extends SceneElementImpl { + + public static final String CLASS_NAME = "link"; + + private static final String ARROWHEAD = "marker-arrowheads"; + + private final String name; + + private final SceneElement source; + + private final SceneElement target; + + /** Describes link between two blocks. */ + public Link(String name, By selector) { + super(selector); + this.name = name; + SelenideElement source = $(selector).find(By.className(ARROWHEAD)).find(By.cssSelector(":nth-child(1)")); + this.source = new SceneElementImpl(By.id(source.attr("id"))); + SelenideElement target = $(selector).find(By.className(ARROWHEAD)).find(By.cssSelector(":nth-child(2)")); + this.target = new SceneElementImpl(By.id(target.attr("id"))); + } + + public String getName() { + return name; + } + + public SceneElement getSource() { + return source; + } + + public SceneElement getTarget() { + return target; + } + + @Override + public Coordinate getCoordinateOnScene() throws ElementNotOnTheSceneException { + return new Coordinate( + (source.getCoordinateOnScene().getXAbsolute() + target.getCoordinateOnScene().getXAbsolute()) / 2, + (source.getCoordinateOnScene().getYAbsolute() + target.getCoordinateOnScene().getYAbsolute()) / 2 + ); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElement.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElement.java new file mode 100644 index 00000000..cba5a1cf --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElement.java @@ -0,0 +1,15 @@ +package com.qreal.wmp.uitesting.dia.scene.elements; + +import com.codeborne.selenide.SelenideElement; +import com.qreal.wmp.uitesting.dia.scene.Coordinate; +import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException; +import org.openqa.selenium.By; + +/** Describes any element on the Scene. */ +public interface SceneElement { + SelenideElement getInnerSeleniumElement(); + + By getBy(); + + Coordinate getCoordinateOnScene() throws ElementNotOnTheSceneException; +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElementImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElementImpl.java new file mode 100644 index 00000000..7d0d99d4 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/elements/SceneElementImpl.java @@ -0,0 +1,36 @@ +package com.qreal.wmp.uitesting.dia.scene.elements; + +import com.codeborne.selenide.SelenideElement; +import com.qreal.wmp.uitesting.dia.scene.Coordinate; +import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException; +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Selenide.$; + +/** + * All Scene elements have selector by which we can clearly define their web instances. + * Also all scene elements have coordinates on the Scene. + */ +public class SceneElementImpl implements SceneElement { + + // Wrapper over an string selector. Used to search the element in HTML representation of current page. + private final By selector; + + public SceneElementImpl(By selector) { + this.selector = selector; + } + + /** Based on the Selenium element. */ + public SelenideElement getInnerSeleniumElement() { + return $(selector); + } + + public By getBy() { + return selector; + } + + public Coordinate getCoordinateOnScene() throws ElementNotOnTheSceneException { + return Coordinate.getCoordinateFromSeleniumObject(getInnerSeleniumElement()) + .orElseThrow(ElementNotOnTheSceneException::new); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/BlockProvider.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/BlockProvider.java new file mode 100644 index 00000000..c098da99 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/BlockProvider.java @@ -0,0 +1,93 @@ +package com.qreal.wmp.uitesting.dia.scene.providers; + +import com.codeborne.selenide.SelenideElement; +import com.qreal.wmp.uitesting.dia.scene.Coordinate; +import com.qreal.wmp.uitesting.dia.scene.Scene; +import com.qreal.wmp.uitesting.dia.scene.elements.Block; +import com.qreal.wmp.uitesting.dia.scene.window.SceneWindow; +import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException; +import org.jetbrains.annotations.Contract; +import org.openqa.selenium.By; +import org.openqa.selenium.NotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.codeborne.selenide.Selenide.$$; + +/** Encapsulates blocks operations. */ +public class BlockProvider { + + private static final Logger logger = LoggerFactory.getLogger(BlockProvider.class); + + private final SceneWindow sceneWindow; + + private final String selector; + + private final Scene scene; + + private Set blocks = new HashSet<>(); + + private BlockProvider(SceneWindow sceneWindow, String selector, Scene scene) { + this.sceneWindow = sceneWindow; + this.selector = selector; + this.scene = scene; + } + + /** Move element to cell with x and y coordinates. */ + public void moveToCell(final Block block, final int cellX, final int cellY) { + logger.info("Move element {} to cell ({}, {})", block, cellX, cellY); + try { + sceneWindow.move(block, + new Coordinate(cellX * Coordinate.POINT_IN_CELL, cellY * Coordinate.POINT_IN_CELL)); + } catch (ElementNotOnTheSceneException e) { + logger.error("It is impossible to move element, which is not on the Scene"); + } + } + + public List getBlocks() { + return Collections.unmodifiableList(blocks.stream().collect(Collectors.toList())); + } + + /** Return added block. */ + public Block getNewBlock() { + final SelenideElement newEl = updateBlocks().orElseThrow(NotFoundException::new); + logger.info("Add element {} to scene", newEl); + Block block = new Block(newEl.attr("id"), By.id(newEl.attr("id")), scene); + blocks.add(block); + return block; + } + + public boolean exist(Block block) { + return blocks.stream().anyMatch(anyBlock -> anyBlock.getName().equals(block.getName())); + } + + public boolean isEmpty() { + return blocks.isEmpty(); + } + + public void recalculateBlocks() { + blocks = $$(By.cssSelector(selector + " #v_7 > *")).stream() + .filter(x -> x.attr("class").contains(Block.CLASS_NAME)) + .map(x -> new Block(x.attr("id"), By.id(x.attr("id")), scene)) + .collect(Collectors.toSet()); + } + + @Contract("_, _, _ -> !null") + public static BlockProvider getBlockProvider(SceneWindow sceneWindow, String selector, Scene scene) { + return new BlockProvider(sceneWindow, selector, scene); + } + + /** Return new element of the scene. */ + private Optional updateBlocks() { + final List allElements = $$(By.cssSelector(selector + " #v_7 > *")); + return allElements.stream() + .filter(htmlElement -> + htmlElement.attr("class").contains("element devs ImageWithPorts") && + blocks.stream().noneMatch(block -> block.getInnerSeleniumElement() + .attr("id").equals(htmlElement.attr("id"))) + ).findFirst(); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/LinkProvider.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/LinkProvider.java new file mode 100644 index 00000000..8e193643 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/providers/LinkProvider.java @@ -0,0 +1,84 @@ +package com.qreal.wmp.uitesting.dia.scene.providers; + +import com.codeborne.selenide.SelenideElement; +import com.qreal.wmp.uitesting.dia.scene.elements.Block; +import com.qreal.wmp.uitesting.dia.scene.elements.Link; +import org.jetbrains.annotations.Contract; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.interactions.Actions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + +/** Encapsulates links operations. */ +public class LinkProvider { + + private static final Logger logger = LoggerFactory.getLogger(LinkProvider.class); + + private final String selector; + + private final WebDriver webDriver; + + private Set links = new HashSet<>(); + + private LinkProvider(String selector, WebDriver webDriver) { + this.selector = selector; + this.webDriver = webDriver; + } + + public List getLinks() { + return Collections.unmodifiableList(links.stream().collect(Collectors.toList())); + } + + public boolean isEmpty() { + return links.isEmpty(); + } + + public boolean exist(Link link) { + return links.stream().anyMatch(anyLink -> anyLink.getName().equals(link.getName())); + } + + /** Add link between two blocks. */ + public Link addLink(final Block source, final Block target) { + final SelenideElement begin = $(By.cssSelector(selector + " #" + + source.getInnerSeleniumElement().attr("id") + " .outPorts")); + logger.info("Begin element {}, end element {} ", begin, target); + new Actions(webDriver) + .release() + .dragAndDrop(source.getPort().getInnerSeleniumElement(), target.getInnerSeleniumElement()) + .build().perform(); + SelenideElement newEl = updateLinks().orElseThrow(() -> new NoSuchElementException("Link was not created")); + logger.info("Add link {}", newEl); + Link res = new Link(newEl.attr("id"), By.id(newEl.attr("id"))); + links.add(res); + return res; + } + + public void recalculateLinks() { + links = $$(By.cssSelector(selector + " #v_7 > *")).stream() + .filter(x -> x.attr("class").contains(Link.CLASS_NAME)) + .map(x -> new Link(x.attr("id"), By.id(x.attr("id")))) + .collect(Collectors.toSet()); + } + + @Contract("_, _ -> !null") + public static LinkProvider getLinkProvider(String selector, WebDriver webDriver) { + return new LinkProvider(selector, webDriver); + } + + private Optional updateLinks() { + final List allElements = $$(By.cssSelector(selector + " #v_7 > *")); + return allElements.stream() + .filter(htmlElement -> + htmlElement.attr("class").contains("link") && + links.stream().noneMatch(link -> htmlElement.attr("id") + .equals(link.getInnerSeleniumElement().attr("id"))) + ).findFirst(); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindow.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindow.java new file mode 100644 index 00000000..ebe5d8e9 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindow.java @@ -0,0 +1,23 @@ +package com.qreal.wmp.uitesting.dia.scene.window; + +import com.qreal.wmp.uitesting.dia.scene.Coordinate; +import com.qreal.wmp.uitesting.dia.scene.elements.Block; +import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException; + +public interface SceneWindow { + + /** + * Moves element to the requested position. + * + * @param element element to move + * @param dist position to move + * */ + void move(final Block element, final Coordinate dist) throws ElementNotOnTheSceneException; + + /** + * Move the screen to requested position. + * + * @param coordinate coordinate to move + */ + void focus(final Coordinate coordinate); +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindowImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindowImpl.java new file mode 100644 index 00000000..adee0086 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/dia/scene/window/SceneWindowImpl.java @@ -0,0 +1,187 @@ +package com.qreal.wmp.uitesting.dia.scene.window; + +import com.google.common.base.Predicate; +import com.qreal.wmp.uitesting.dia.scene.Coordinate; +import com.qreal.wmp.uitesting.dia.scene.elements.Block; +import com.qreal.wmp.uitesting.dia.scene.elements.SceneElement; +import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException; +import org.jetbrains.annotations.Contract; +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Random; + +import static com.codeborne.selenide.Selenide.$; + +/** + * Describes part of the scene, which is shown on browser. + */ +public class SceneWindowImpl implements SceneWindow { + + private static final Logger logger = LoggerFactory.getLogger(SceneWindowImpl.class); + + private final WebDriver driver; + + /** Constructor takes links to current scene and current driver. */ + private SceneWindowImpl(final WebDriver driver) { + this.driver = driver; + } + + /** + * Moves element to the requested position. + * + * @param element element to move + * @param dist position to move + */ + @Override + public void move(final Block element, final Coordinate dist) throws ElementNotOnTheSceneException { + Coordinate src = element.getCoordinateOnScene(); + focus(src); + int sizeHor = Double.valueOf($(By.id("SceneWindowHorSize")).innerHtml()).intValue(); + int sizeVer = Double.valueOf($(By.id("SceneWindowVerSize")).innerHtml()).intValue(); + + Actions actions = new Actions(driver); + actions.clickAndHold(element.getInnerSeleniumElement()); + if (isDistanceLessThenBarrier( + dist, + element.getCoordinateOnScene(), + new OffsetObject(sizeHor / 2, sizeVer / 2))) { + + jump(actions, element, dist); + } else { + java.util.function.Predicate condX = x -> + isDistanceLessThenBarrier(x.getXAbsolute(), dist.getXAbsolute(), sizeHor / 3); + + if (src.getXAbsolute() < dist.getXAbsolute()) { + innerMove(actions, element, new OffsetObject(sizeHor / 4, 0), condX); + } else { + innerMove(actions, element, new OffsetObject(-sizeHor / 4, 0), condX); + } + } + if (Math.abs(dist.getXAbsolute() - element.getCoordinateOnScene().getXAbsolute()) < sizeHor / 2 + && Math.abs(dist.getYAbsolute() - element.getCoordinateOnScene().getYAbsolute()) < sizeVer / 2) { + jump(actions, element, dist); + } else { + java.util.function.Predicate condY = x -> + isDistanceLessThenBarrier(x.getYAbsolute(), dist.getYAbsolute(), sizeVer / 3); + if (src.getYAbsolute() < dist.getYAbsolute()) { + innerMove(actions, element, new OffsetObject(0, sizeVer / 4), condY); + } else { + innerMove(actions, element, new OffsetObject(0, -sizeVer / 4), condY); + } + } + + jump(actions, element, dist); + + (new WebDriverWait(driver, 40)) + .until((Predicate) webDriver -> { + try { + return element.getCoordinateOnScene().equals(dist); + } catch (ElementNotOnTheSceneException e) { + logger.error("It is impossible to move element, which is not on the Scene"); + } + return false; + }); + } + + @Override + public void focus(final Coordinate coordinate) { + updateCanvasInfo(); + int sizeHor = Double.valueOf($(By.id("SceneWindowHorSize")).innerHtml()).intValue(); + int sizeVer = Double.valueOf($(By.id("SceneWindowVerSize")).innerHtml()).intValue(); + + logger.info("Focus to " + coordinate.getXAbsolute() + " " + coordinate.getYAbsolute()); + if (driver instanceof JavascriptExecutor) { + ((JavascriptExecutor) driver).executeScript("var canvas = " + + "document.getElementsByClassName(\"scene-wrapper\")[0]; " + + "var BB=canvas.getBoundingClientRect();" + + "canvas.scrollLeft = " + Math.max(0, (coordinate.getXAbsolute() - sizeHor / 2)) + "; " + + "canvas.scrollTop = " + Math.max(0, (coordinate.getYAbsolute() - sizeVer / 2)) + ";" + ); + } + updateCanvasInfo(); + } + + @Contract("_ -> !null") + public static SceneWindow getSceneWindow(WebDriver webDriver) { + return new SceneWindowImpl(webDriver); + } + + private void updateCanvasInfo() { + if (driver instanceof JavascriptExecutor) { + ((JavascriptExecutor) driver).executeScript("var canvas = " + + "document.getElementsByClassName(\"scene-wrapper\")[0]; " + + "var BB=canvas.getBoundingClientRect();" + + "$('#SceneWindowLeft').html(canvas.scrollLeft);" + + "$('#SceneWindowTop').html(canvas.scrollTop);" + + "$('#SceneWindowHorSize').html(BB.right - BB.left);" + + "$('#SceneWindowVerSize').html(BB.bottom - BB.top);" + ); + } + } + + private void innerMove(Actions actions, SceneElement element, + OffsetObject offset, java.util.function.Predicate cond) { + + (new WebDriverWait(driver, 40)) + .until((Predicate) webDriver -> { + try { + Coordinate current = element.getCoordinateOnScene(); + actions.moveToElement(element.getInnerSeleniumElement()).perform(); + actions.moveByOffset(offset.offsetX, offset.offsetY).perform(); + + focus(element.getCoordinateOnScene()); + if (element.getCoordinateOnScene().equals(current)) { + Random random = new Random(); + int signX = (int) Math.signum(offset.offsetX); + int signY = (int) Math.signum(offset.offsetY); + if (offset.offsetX != 0) { + offset.offsetX = signX * random.nextInt(Math.abs(offset.offsetX)); + } + if (offset.offsetY != 0) { + offset.offsetY = signY * random.nextInt(Math.abs(offset.offsetY)); + } + } + return cond.test(element.getCoordinateOnScene()); + } catch (ElementNotOnTheSceneException e) { + logger.error("It is impossible to move element, which is not on the Scene"); + } + return false; + }); + } + + private void jump(Actions actions, SceneElement element, Coordinate dist) throws ElementNotOnTheSceneException { + if (!element.getCoordinateOnScene().equals(dist)) { + focus(element.getCoordinateOnScene()); + actions.moveToElement(element.getInnerSeleniumElement()).moveByOffset( + dist.getXAbsolute() - element.getCoordinateOnScene().getXAbsolute(), + dist.getYAbsolute() - element.getCoordinateOnScene().getYAbsolute() + ).release().build().perform(); + } + } + + private class OffsetObject { + private int offsetX; + + private int offsetY; + + OffsetObject(int offsetX, int offsetY) { + this.offsetX = offsetX; + this.offsetY = offsetY; + } + } + + private boolean isDistanceLessThenBarrier(double fst, double snd, double barrier) { + return Math.abs(fst - snd) <= barrier; + } + + private boolean isDistanceLessThenBarrier(Coordinate fst, Coordinate snd, OffsetObject barrier) { + return isDistanceLessThenBarrier(fst.getXAbsolute(), snd.getXAbsolute(), barrier.offsetX) && + isDistanceLessThenBarrier(fst.getYAbsolute(), snd.getYAbsolute(), barrier.offsetY); + } +} \ No newline at end of file diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/ElementNotOnTheSceneException.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/ElementNotOnTheSceneException.java new file mode 100644 index 00000000..53197584 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/ElementNotOnTheSceneException.java @@ -0,0 +1,9 @@ +package com.qreal.wmp.uitesting.exceptions; + +public class ElementNotOnTheSceneException extends Exception { + + public ElementNotOnTheSceneException() { + super("It is impossible to get Coordinates of element, which is not on the Scene"); + } + +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/WrongAuthException.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/WrongAuthException.java new file mode 100644 index 00000000..ec2aa1d1 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/exceptions/WrongAuthException.java @@ -0,0 +1,9 @@ +package com.qreal.wmp.uitesting.exceptions; + +/** Throw if we cannot authorize. */ +public class WrongAuthException extends Exception { + + public WrongAuthException(final String login, final String password) { + super("Unable to authorize with login: " + login + " and password: " + password); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/AuthPage.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/AuthPage.java new file mode 100644 index 00000000..656f42de --- /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 { +} 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..bc24f6f3 --- /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 { +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/EditorPage.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/EditorPage.java new file mode 100644 index 00000000..e3422a40 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/pages/EditorPage.java @@ -0,0 +1,35 @@ +package com.qreal.wmp.uitesting.pages; + +import com.qreal.wmp.uitesting.dia.palette.Palette; +import com.qreal.wmp.uitesting.dia.property.PropertyEditor; +import com.qreal.wmp.uitesting.dia.scene.Scene; + +/** Describes Editor page of the WMP project. + * Includes such components as Scene, Palette and PropertyEditor. + */ +public class EditorPage { + + private final Scene scene; + + private final Palette palette; + + private final PropertyEditor propertyEditor; + + public EditorPage(Scene scene, Palette palette, PropertyEditor propertyEditor) { + this.scene = scene; + this.palette = palette; + this.propertyEditor = propertyEditor; + } + + public Scene getScene() { + return scene; + } + + public Palette getPalette() { + return palette; + } + + public PropertyEditor getPropertyEditor() { + return propertyEditor; + } +} 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..99656a36 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Auther.java @@ -0,0 +1,17 @@ +package com.qreal.wmp.uitesting.services; + +import com.qreal.wmp.uitesting.exceptions.WrongAuthException; + +/** Used for authentication in current browser session. */ +public interface Auther { + + /** Implements authentication to the wmp. + * + * @param username login + * @param password password + * */ + void auth(final String username, final String password) throws WrongAuthException; + + /** Authentication with fixed login and password. */ + void auth() throws WrongAuthException; +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Opener.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Opener.java new file mode 100644 index 00000000..4ae30ae6 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/Opener.java @@ -0,0 +1,21 @@ +package com.qreal.wmp.uitesting.services; + +/** + * Used for open needed page from wmp in current browser session. + * Allows you to access as an authorized user and not. + */ +public interface Opener { + /** + * Opens page from wmp with authentication. + * + * @param page must be one of the keys from pages.property. + */ + void open(final String page); + + /** + * Opens page from wmp without authentication. + * + * @param page must be one of the keys from pages.property. + */ + void cleanOpen(final String page); +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/AutherImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/AutherImpl.java new file mode 100644 index 00000000..382c52bf --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/AutherImpl.java @@ -0,0 +1,41 @@ +package com.qreal.wmp.uitesting.services.impl; + +import com.qreal.wmp.uitesting.exceptions.WrongAuthException; +import com.qreal.wmp.uitesting.services.Auther; +import org.openqa.selenium.By; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; + +import static com.codeborne.selenide.Selectors.byText; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.open; + +/** {@inheritDoc} */ +public class AutherImpl implements Auther { + + /** Use properties from pages.properies file. */ + private Environment env; + + private static final Logger logger = LoggerFactory.getLogger(AutherImpl.class); + + public AutherImpl(Environment env) { + this.env = env; + } + + /** {@inheritDoc} */ + public void auth(final String username, final String password) throws WrongAuthException { + open(env.getProperty("auth")); + $(By.name("username")).setValue(username); + $(By.name("password")).setValue(password); + $("[type=\"submit\"]").click(); + if ($(byText("Password or login wrong")).exists()) { + throw new WrongAuthException(username, password); + } + logger.info("Authentication with login: {} and password: {}", username, password); + } + + public void auth() throws WrongAuthException { + auth("Admin", "Admin"); + } +} diff --git a/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/OpenerImpl.java b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/OpenerImpl.java new file mode 100644 index 00000000..a5ebf771 --- /dev/null +++ b/ui-testing/src/main/java/com/qreal/wmp/uitesting/services/impl/OpenerImpl.java @@ -0,0 +1,51 @@ +package com.qreal.wmp.uitesting.services.impl; + +import com.qreal.wmp.uitesting.exceptions.WrongAuthException; +import com.qreal.wmp.uitesting.services.Auther; +import com.qreal.wmp.uitesting.services.Opener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.security.access.AccessDeniedException; + +import static com.codeborne.selenide.Selectors.byText; +import static com.codeborne.selenide.Selenide.$; + +/** {@inheritDoc} */ +public class OpenerImpl implements Opener { + + /** Uses properties from pages.properies file. */ + private Environment env; + + private Auther auther; + + private static final Logger logger = LoggerFactory.getLogger(OpenerImpl.class); + + public OpenerImpl(Environment env, Auther auther) { + this.env = env; + this.auther = auther; + } + + /** {@inheritDoc} */ + public void open(final String page) { + try { + com.codeborne.selenide.Selenide.open(env.getProperty(page)); + logger.info("Open page {}", env.getProperty(page)); + if ($(byText("Sign in to continue to Auth")).exists()) { + logger.info("Fail with open page {}. Try to login.", env.getProperty(page)); + auther.auth(); + } + com.codeborne.selenide.Selenide.open(env.getProperty(page)); + logger.info("Open page {}", env.getProperty(page)); + } catch (WrongAuthException e) { + logger.error("Opener fails: " + e.getMessage()); + throw new AccessDeniedException(e.getMessage()); + } + logger.info("Open page {}", env.getProperty(page)); + } + + public void cleanOpen(final String page) { + com.codeborne.selenide.Selenide.open(env.getProperty(page)); + logger.info("Open page {}", env.getProperty(page)); + } +} diff --git a/ui-testing/src/main/resources/pages.properties b/ui-testing/src/main/resources/pages.properties new file mode 100644 index 00000000..0ffeabe3 --- /dev/null +++ b/ui-testing/src/main/resources/pages.properties @@ -0,0 +1,4 @@ +auth=http://localhost:${port.auth}${path.auth} +dashboard=http://localhost:${port.dashboard}${path.dashboard} +robotsEditor=http://localhost:${port.editor}${path.editor.robots} +bpmnEditor=http://localhost:${port.editor}${path.editor.bpmn} \ No newline at end of file diff --git a/ui-testing/src/main/resources/services.properties b/ui-testing/src/main/resources/services.properties deleted file mode 100644 index 1382b546..00000000 --- a/ui-testing/src/main/resources/services.properties +++ /dev/null @@ -1 +0,0 @@ -accessDashboardUri=http://localhost:${port.dashboard}${path.dashboard} \ No newline at end of file diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/auth/AuthDashboardTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/auth/AuthDashboardTest.java deleted file mode 100644 index b05f4547..00000000 --- a/ui-testing/src/test/java/com/qreal/wmp/uitesting/auth/AuthDashboardTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.qreal.wmp.uitesting.auth; - -import com.codeborne.selenide.WebDriverRunner; -import io.github.bonigarcia.wdm.ChromeDriverManager; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.*; -import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.chrome.ChromeDriver; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -import static com.codeborne.selenide.Condition.appear; -import static com.codeborne.selenide.Condition.exist; -import static com.codeborne.selenide.Selectors.byText; -import static com.codeborne.selenide.Selenide.$; -import static com.codeborne.selenide.Selenide.open; - -public class AuthDashboardTest { - - private static String dashboardUrl; - - private static WebDriver driver; - - /** - * Setup ChromeDriverManager and load correct urls from .properties file. - */ - @BeforeClass - public static void setUpClass() { - ChromeDriverManager.getInstance().setup(); - final String resourceName = "services.properties"; - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - Properties props = new Properties(); - try (InputStream resourceStream = loader.getResourceAsStream(resourceName)) { - props.load(resourceStream); - } catch (IOException e) { - e.printStackTrace(); - } - dashboardUrl = props.getProperty("accessDashboardUri"); - } - - /** - * Try to open dashboard page. - * Should be redirected to auth page. - */ - @Before - public void openAuthPage() { - driver = new ChromeDriver(); - WebDriverRunner.setWebDriver(driver); - open(dashboardUrl); - $(byText("Sign in to continue to Auth")).shouldBe(exist); - $(byText("Dashboard")).shouldNotBe(exist); - } - - /** - * Try to login with correct username and password. - * Should access and redirect to dashboard - */ - @Test - public void userCanLoginByUsername() { - $(By.name("username")).setValue("123"); - $(By.name("password")).setValue("123"); - $("[type=\"submit\"]").click(); - $(byText("Dashboard")).waitUntil(appear, 50000); - } - - /** - * Try to login with random username and password. - * An error must be shown - */ - @Test - public void userWrongAuth() { - $(byText("Password or login wrong")).shouldNotBe(exist); - char[] alphabet = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray(); - String wrongLogin = RandomStringUtils.random(20, alphabet); - String wrongPassword = RandomStringUtils.random(20, alphabet); - $(By.name("username")).setValue(wrongLogin); - $(By.name("password")).setValue(wrongPassword); - $("[type=\"submit\"]").click(); - $(byText("Password or login wrong")).shouldBe(exist); - } - - @After - public void logout() { - driver.close(); - } - -} diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/AuthTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/AuthTest.java new file mode 100644 index 00000000..5759ade8 --- /dev/null +++ b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/AuthTest.java @@ -0,0 +1,124 @@ +package com.qreal.wmp.uitesting.innertests; + +import com.qreal.wmp.uitesting.config.AppInit; +import com.qreal.wmp.uitesting.exceptions.WrongAuthException; +import com.qreal.wmp.uitesting.services.Auther; +import com.qreal.wmp.uitesting.services.Opener; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import static com.codeborne.selenide.Condition.appear; +import static com.codeborne.selenide.Selectors.byText; +import static com.codeborne.selenide.Selenide.$; + +/** Tests for opener and auther services. */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = AppInit.class, loader = AnnotationConfigContextLoader.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class AuthTest { + + private static final String WRONG_LOGIN = "lbltfn16vup5boj7o1ju"; + + private static final String WRONG_PASSWORD = "8epo7li9uq5vs3wujpm4"; + + @Autowired + private Auther auther; + + @Autowired + private Opener opener; + + /** + * Try to login with correct username and password. + * Should redirect to OAuth page. + */ + @Test + public void authTest() { + try { + opener.cleanOpen("auth"); + assert inAuthPage(); + auther.auth(); + assert $(byText("OAuth Server")).waitUntil(appear, 5000).exists(); + } catch (WrongAuthException e) { + System.err.println(e.getMessage()); + } + } + + /** + * Try to login with random username and password. + * An error must be shown. + */ + @Test + public void authWrongTest() { + opener.cleanOpen("auth"); + assert inAuthPage(); + try { + auther.auth(WRONG_LOGIN, WRONG_PASSWORD); + } catch (WrongAuthException e) { + System.err.println(e.getMessage()); + } + $(byText("Password or login wrong")).waitUntil(appear, 5000); + } + + /** + * Try to open dashboard page without authentication. + * Should be redirected to auth page. + * Try to open dashboard page with correct login and password. + */ + @Test + public void dashboardTest() { + opener.cleanOpen("dashboard"); + assert inAuthPage(); + opener.open("dashboard"); + $(byText("Dashboard")).waitUntil(appear, 5000); + } + + /** + * Try to open robots-editor page without authentication. + * Should be redirected to auth page. + * Try to open robots-editor page with correct login and password. + */ + @Test + public void robotsEditorTest() { + opener.cleanOpen("robotsEditor"); + assert inAuthPage(); + opener.open("robotsEditor"); + $(byText("Property Editor")).waitUntil(appear, 5000); + } + + /** + * Try to open bpmn-editor page without authentication. + * Should be redirected to auth page. + * Try to open bpmn-editor page with correct login and password. + */ + @Test + public void bpmnEditorTest() { + opener.cleanOpen("bpmnEditor"); + assert inAuthPage(); + opener.open("bpmnEditor"); + $(byText("Property Editor")).waitUntil(appear, 5000); + } + + /** + * Check that current page is Auth page. + * + * @return true if it is + */ + private boolean inAuthPage() { + return $(byText("Sign in to continue to Auth")).exists(); + } + + /** To generate random login and password. */ + public static void main(String[] args) { + final char[] alphabet = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray(); + final String wrongLogin = RandomStringUtils.random(20, alphabet); + final String wrongPassword = RandomStringUtils.random(20, alphabet); + System.out.println(wrongLogin); + System.out.println(wrongPassword); + } +} diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/ManipulatingDiagramTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/ManipulatingDiagramTest.java new file mode 100644 index 00000000..407b2209 --- /dev/null +++ b/ui-testing/src/test/java/com/qreal/wmp/uitesting/innertests/ManipulatingDiagramTest.java @@ -0,0 +1,112 @@ +package com.qreal.wmp.uitesting.innertests; + +import com.qreal.wmp.uitesting.Page; +import com.qreal.wmp.uitesting.PageLoader; +import com.qreal.wmp.uitesting.config.AppInit; +import com.qreal.wmp.uitesting.dia.palette.Palette; +import com.qreal.wmp.uitesting.dia.property.PropertyEditor; +import com.qreal.wmp.uitesting.dia.scene.Scene; +import com.qreal.wmp.uitesting.dia.scene.elements.Block; +import com.qreal.wmp.uitesting.dia.scene.elements.Link; +import com.qreal.wmp.uitesting.exceptions.ElementNotOnTheSceneException; +import com.qreal.wmp.uitesting.pages.EditorPage; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = AppInit.class, loader = AnnotationConfigContextLoader.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class ManipulatingDiagramTest { + + private static final Logger logger = LoggerFactory.getLogger(ManipulatingDiagramTest.class); + + @Autowired + private PageLoader pageLoader; + + private Palette palette; + + private Scene scene; + + private PropertyEditor propertyEditor; + + /** Open editor page. */ + @Before + public void openEditor() { + EditorPage editorPage = pageLoader.load(Page.EditorRobots); + scene = editorPage.getScene(); + palette = editorPage.getPalette(); + propertyEditor = editorPage.getPropertyEditor(); + } + + /** Drag element from palette and drop on the scene. */ + @Test + public void dragAndDrop() { + final Block initialNode = scene.dragAndDrop(palette.getElement("Initial Node")); + assert scene.exist(initialNode); + } + + /** Remove element from scene. */ + @Test + public void remove() { + final Block sceneElement = scene.dragAndDrop(palette.getElement("Initial Node")); + assert scene.exist(sceneElement); + try { + scene.remove(sceneElement); + } catch (ElementNotOnTheSceneException e) { + logger.error(e.getMessage()); + } + assert !scene.exist(sceneElement); + } + + /** Add two elements and link them. */ + @Test + public void addLink() { + final Block initNode = scene.dragAndDrop(palette.getElement("Initial Node"), 4, 4); + final Block finalNode = scene.dragAndDrop(palette.getElement("Final Node"), 4, 70); + final Block motor = scene.dragAndDrop(palette.getElement("Motors Forward"), 4, 7); + Link link = scene.addLink(initNode, motor); + Link link2 = scene.addLink(motor, finalNode); + motor.moveToCell(72, 64); + assert scene.exist(link); + assert scene.exist(link2); + } + + /** Set property 'Ports' of motor forward item to '123' and checks that all is correct. */ + @Test + public void propertyEditor() { + final Block motor = scene.dragAndDrop(palette.getElement("Motors Forward")); + propertyEditor.setProperty(motor.getInnerSeleniumElement(), "Ports", "123"); + assert propertyEditor.getProperty(motor.getInnerSeleniumElement(), "Ports").equals("123"); + } + + /** Move element to cell. */ + @Test + public void moveElement() { + final Block motor = scene.dragAndDrop(palette.getElement("Motors Forward")); + try { + motor.moveToCell(40, 40); + assert motor.getCoordinateOnScene().getXCell() == 40 && motor.getCoordinateOnScene().getYCell() == 40; + motor.moveToCell(72, 64); + assert motor.getCoordinateOnScene().getXCell() == 72 && motor.getCoordinateOnScene().getYCell() == 64; + motor.moveToCell(0, 0); + assert motor.getCoordinateOnScene().getXCell() == 0 && motor.getCoordinateOnScene().getYCell() == 0; + } catch (ElementNotOnTheSceneException e) { + logger.error(e.getMessage()); + } + } + + /** Clean scene. */ + @After + public void cleanScene() { + scene.clean(); + } +} diff --git a/ui-testing/src/test/java/com/qreal/wmp/uitesting/testspace/DiagramConstructingTest.java b/ui-testing/src/test/java/com/qreal/wmp/uitesting/testspace/DiagramConstructingTest.java new file mode 100644 index 00000000..cb68505a --- /dev/null +++ b/ui-testing/src/test/java/com/qreal/wmp/uitesting/testspace/DiagramConstructingTest.java @@ -0,0 +1,95 @@ +package com.qreal.wmp.uitesting.testspace; + +import com.qreal.wmp.uitesting.Page; +import com.qreal.wmp.uitesting.PageLoader; +import com.qreal.wmp.uitesting.config.AppInit; +import com.qreal.wmp.uitesting.dia.palette.Palette; +import com.qreal.wmp.uitesting.dia.property.PropertyEditor; +import com.qreal.wmp.uitesting.dia.scene.Scene; +import com.qreal.wmp.uitesting.dia.scene.elements.Block; +import com.qreal.wmp.uitesting.dia.scene.elements.Link; +import com.qreal.wmp.uitesting.pages.EditorPage; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import java.util.ArrayList; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = AppInit.class, loader = AnnotationConfigContextLoader.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +public class DiagramConstructingTest { + + @Autowired + private PageLoader pageLoader; + + private Scene scene; + + private PropertyEditor propertyEditor; + + private ArrayList elements; + + private ArrayList links; + + /** Open editor page. */ + @Before + public void openEditor() { + EditorPage editorPage = pageLoader.load(Page.EditorRobots); + scene = editorPage.getScene(); + Palette palette = editorPage.getPalette(); + propertyEditor = editorPage.getPropertyEditor(); + + elements = new ArrayList<>(); + links = new ArrayList<>(); + + elements.add(scene.dragAndDrop(palette.getElement("Initial Node"), 4, 4)); + elements.add(scene.dragAndDrop(palette.getElement("Motors Forward"), 10, 4)); + links.add(scene.addLink(elements.get(0), elements.get(1))); + elements.add(scene.dragAndDrop(palette.getElement("Painter Color"), 16, 4)); + links.add(scene.addLink(elements.get(1), elements.get(2))); + elements.add(scene.dragAndDrop(palette.getElement("Timer"), 22, 4)); + links.add(scene.addLink(elements.get(2), elements.get(3))); + elements.add(scene.dragAndDrop(palette.getElement("Final Node"), 28, 4)); + links.add(scene.addLink(elements.get(3), elements.get(4))); + } + + @Test + public void digramFiveNodes() { + assert allExist(); + } + + @Test + public void moveSomeNodes() { + elements.get(1).moveToCell(20, 20); + elements.get(0).moveToCell(20, 10); + elements.get(1).moveToCell(0, 20); + assert allExist(); + } + + @Test + public void fillProperties() { + propertyEditor.setProperty(elements.get(1).getInnerSeleniumElement(), "Power", "80"); + assert propertyEditor.getProperty(elements.get(1).getInnerSeleniumElement(), "Power").equals("80"); + propertyEditor.setProperty(elements.get(2).getInnerSeleniumElement(), "Color", "green"); + assert propertyEditor.getProperty(elements.get(2).getInnerSeleniumElement(), "Color").equals("green"); + propertyEditor.setProperty(elements.get(3).getInnerSeleniumElement(), "Delay", "200"); + assert propertyEditor.getProperty(elements.get(3).getInnerSeleniumElement(), "Delay").equals("200"); + } + + /** Clean scene. */ + @After + public void cleanScene() { + scene.clean(); + } + + private boolean allExist() { + return elements.stream().allMatch(scene::exist) && links.stream().anyMatch(scene::exist); + } + +} diff --git a/ui-testing/ui-testing.iml b/ui-testing/ui-testing.iml index fe03b019..62ae7fcf 100644 --- a/ui-testing/ui-testing.iml +++ b/ui-testing/ui-testing.iml @@ -1,5 +1,5 @@ - + @@ -12,52 +12,70 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +