diff --git a/build.gradle b/build.gradle
index 1dc8742bd3..07f439f4c2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -38,6 +38,7 @@ def guiModule = project(':cuba-gui')
def webToolkitModule = project(':cuba-web-toolkit')
def webModule = project(':cuba-web')
def desktopModule = project(':cuba-desktop')
+def acceptanceModule = project(':cuba-test-ui')
def servletApi = [group: 'org.apache.tomcat', name: 'servlet-api', version: '6.0.20']
def vaadin = [group: 'com.haulmont.thirdparty', name: 'vaadin', version: '6.6.1.91']
@@ -54,7 +55,7 @@ def postgres = [group: 'postgresql', name: 'postgresql', version: '8.3-603.jdbc4
def hsqldb = [group: 'hsqldb', name: 'hsqldb', version: '1.8.0.10']
configure([sharedLibModule, globalModule, coreModule, clientModule, guiModule, webToolkitModule,
- webModule, desktopModule]) {
+ webModule, desktopModule, acceptanceModule]) {
apply(plugin: 'java')
apply(plugin: 'idea')
apply(plugin: 'maven')
@@ -64,23 +65,23 @@ configure([sharedLibModule, globalModule, coreModule, clientModule, guiModule, w
compile(group: 'com.haulmont.bali', name: 'bali', version: '3.0-SNAPSHOT')
compile(group: 'com.haulmont.chile', name: 'chile-core', version: '3.0-SNAPSHOT')
compile(group: 'com.haulmont.chile', name: 'chile-jpa', version: '3.0-SNAPSHOT')
-
+
compile(group: 'antlr', name: 'antlr', version: '2.7.7')
- compile(group: 'aopalliance', name: 'aopalliance', version: '1.0')
+ compile(group: 'aopalliance', name: 'aopalliance', version: '1.0')
compile(asm)
compile(group: 'com.google.guava', name: 'guava', version: 'r07')
compile(group: 'com.haulmont.thirdparty', name: 'openjpa', version: '1.2.2')
- compile(group: 'commons-cli', name: 'commons-cli', version: '1.2')
+ compile(group: 'commons-cli', name: 'commons-cli', version: '1.2')
compile(group: 'commons-pool', name: 'commons-pool', version: '1.5.1')
compile(group: 'jasperreports', name: 'jasperreports', version: '3.5.1', transitive: false)
compile(group: 'jfree', name: 'jfreechart', version: '1.0.13')
- compile(group: 'net.sourceforge.serp', name: 'serp', version: '1.13.1')
- compile(group: 'org.antlr', name: 'antlr-runtime', version: '3.2')
- compile(group: 'org.freemarker', name: 'freemarker', version: '2.3.16')
+ compile(group: 'net.sourceforge.serp', name: 'serp', version: '1.13.1')
+ compile(group: 'org.antlr', name: 'antlr-runtime', version: '3.2')
+ compile(group: 'org.freemarker', name: 'freemarker', version: '2.3.16')
compile(group: 'org.perf4j', name: 'perf4j', version: '0.9.10')
- compile(group: 'org.springframework', name: 'spring-beans', version: '3.0.5.RELEASE')
- compile(group: 'org.springframework', name: 'spring-context', version: '3.0.5.RELEASE')
- compile(group: 'org.springframework', name: 'spring-web', version: '3.0.5.RELEASE')
+ compile(group: 'org.springframework', name: 'spring-beans', version: '3.0.5.RELEASE')
+ compile(group: 'org.springframework', name: 'spring-context', version: '3.0.5.RELEASE')
+ compile(group: 'org.springframework', name: 'spring-web', version: '3.0.5.RELEASE')
testCompile(group: 'junit', name: 'junit', version: '4.5')
}
@@ -88,7 +89,7 @@ configure([sharedLibModule, globalModule, coreModule, clientModule, guiModule, w
task sourceJar(type: Jar) {
from file('src')
classifier = 'sources'
- }
+ }
artifacts {
archives sourceJar
@@ -121,8 +122,9 @@ configure(globalModule) {
configure(coreModule) {
dependencies {
compile(globalModule)
+ compile(acceptanceModule)
compile(sharedLibModule)
- compile(group: 'com.haulmont.thirdparty', name: 'ooo-bootstrapconnector', version: '3.0.0')
+ compile(group: 'com.haulmont.thirdparty', name: 'ooo-bootstrapconnector', version: '3.0.0')
compile(group: 'com.haulmont.thirdparty', name: 'xstream', version: '1.3.1.20100706')
compile(group: 'com.haulmont.thirdparty', name: 'core-renderer', version: 'R8')
runtime(group: 'xpp3', name: 'xpp3_min', version: '1.1.4c')
@@ -130,18 +132,18 @@ configure(coreModule) {
exclude(group: 'bouncycastle', module: 'bcmail-jdk14')
exclude(group: 'bouncycastle', module: 'bcprov-jdk14')
}
- compile(group: 'jgroups', name: 'jgroups', version: '2.9.0.GA')
- compile(group: 'org.apache.commons', name: 'commons-compress', version: '1.1')
+ compile(group: 'jgroups', name: 'jgroups', version: '2.9.0.GA')
+ compile(group: 'org.apache.commons', name: 'commons-compress', version: '1.1')
compile(poi)
- compile(group: 'org.aspectj', name: 'aspectjrt', version: '1.6.6')
- compile(group: 'org.aspectj', name: 'aspectjweaver', version: '1.6.6')
+ compile(group: 'org.aspectj', name: 'aspectjrt', version: '1.6.6')
+ compile(group: 'org.aspectj', name: 'aspectjweaver', version: '1.6.6')
compile(group: 'org.mybatis', name: 'mybatis', version: '3.0.4')
- compile(group: 'org.mybatis', name: 'mybatis-spring', version: '1.0.0')
+ compile(group: 'org.mybatis', name: 'mybatis-spring', version: '1.0.0')
compile(group: 'org.json', name: 'json', version: '20090211')
compile(group: 'org.openoffice', name: 'juh', version: '3.0.0')
- compile(group: 'org.openoffice', name: 'ridl', version: '3.0.0')
- compile(group: 'org.openoffice', name: 'unoil', version: '3.0.0')
- compile(group: 'org.springframework', name: 'spring-context-support', version: '3.0.5.RELEASE')
+ compile(group: 'org.openoffice', name: 'ridl', version: '3.0.0')
+ compile(group: 'org.openoffice', name: 'unoil', version: '3.0.0')
+ compile(group: 'org.springframework', name: 'spring-context-support', version: '3.0.5.RELEASE')
compile(group: 'org.springframework', name: 'spring-orm', version: '3.0.5.RELEASE')
compile(group: 'org.springframework', name: 'spring-tx', version: '3.0.5.RELEASE')
compile(group: 'org.springframework', name: 'spring-webmvc', version: '3.0.5.RELEASE')
@@ -206,7 +208,7 @@ configure(guiModule) {
groovy(groovyArtifact)
groovy(asm)
}
-
+
test {
scanForTestClasses = false
includes = ['**/*Test.class']
@@ -259,7 +261,7 @@ configure(webModule) {
provided(gwtServlets)
provided(validationApi)
provided(servletApi)
-
+
gwtBuilding(validationApi + [classifier: 'sources'])
}
@@ -289,7 +291,7 @@ configure(webModule) {
task buildGwt(dependsOn: buildThemes) << {
File widgetsetsDir = new File(webOutDir, "VAADIN/widgetsets")
File webToolkitSrcDir = new File(webToolkitModule.projectDir, 'src')
-
+
def gwtBuildingArtifacts = configurations.gwtBuilding.resolvedConfiguration.getResolvedArtifacts()
File validationSrcDir = gwtBuildingArtifacts.find { a-> a.name == 'validation-api' }.file
@@ -344,6 +346,11 @@ configure(desktopModule) {
}
}
+configure(acceptanceModule) {
+ dependencies {
+ }
+}
+
task restart(dependsOn: ['stop', ':cuba-core:deploy', ':cuba-web:deploy'], description: 'Redeploys applications and restarts local Tomcat') << {
ant.waitfor(maxwait: 6, maxwaitunit: 'second', checkevery: 2, checkeveryunit: 'second') {
not {
diff --git a/modules/test-ui/src/com/haulmont/cuba/ui/OsUtils.java b/modules/test-ui/src/com/haulmont/cuba/ui/OsUtils.java
new file mode 100644
index 0000000000..1c0e1cd05f
--- /dev/null
+++ b/modules/test-ui/src/com/haulmont/cuba/ui/OsUtils.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
+ * Haulmont Technology proprietary and confidential.
+ * Use is subject to license terms.
+ */
+
+package com.haulmont.cuba.ui;
+
+/**
+ *
$Id$
+ *
+ * @author artamonov
+ */
+public final class OsUtils {
+ private static String OS = null;
+
+ public static String getOsName() {
+ if (OS == null) {
+ OS = System.getProperty("os.name");
+ }
+ return OS;
+ }
+
+ public static boolean isWindows() {
+ return getOsName().startsWith("Windows");
+ }
+}
diff --git a/modules/test-ui/src/com/haulmont/cuba/ui/UITestCase.java b/modules/test-ui/src/com/haulmont/cuba/ui/UITestCase.java
new file mode 100644
index 0000000000..103f20de32
--- /dev/null
+++ b/modules/test-ui/src/com/haulmont/cuba/ui/UITestCase.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
+ * Haulmont Technology proprietary and confidential.
+ * Use is subject to license terms.
+ */
+
+package com.haulmont.cuba.ui;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base test case for UI tests
+ * $Id$
+ *
+ * @author artamonov
+ */
+@SuppressWarnings("unused")
+public abstract class UITestCase extends TestCase {
+
+ /**
+ * Key in system environment variables
+ * Define custom executor script
+ */
+ private static final String ACCEPTANCE_EXECUTOR_KEY = "ACCEPTANCE_EXECUTOR";
+
+ /**
+ * Key in system environment variables
+ * Define custom client program
+ */
+ private static final String ACCEPTANCE_CLIENT_KEY = "ACCEPTANCE_CLIENT";
+
+ private static final String ACCEPTANCE_DIR = "tests/ui/";
+
+ private Process clientRunner;
+
+ public UITestCase() {
+ super();
+ }
+
+ public UITestCase(String name) {
+ super(name);
+ }
+
+ /**
+ * Run UI test by name in test directory
+ * If test fails then makes screenshot and writes log to output directory
+ *
+ * @param testFileName Test name
+ * @return List of test steps with log messages
+ * @throws Exception On IOException or while run client program
+ */
+ protected List runUiTest(String testFileName) throws Exception {
+ String executorScript = getExecutorScript();
+
+ StringBuilder errors = new StringBuilder();
+ StringBuilder info = new StringBuilder();
+ executeTestScript(executorScript, testFileName, errors, info);
+
+ List testSteps = analyzeTestSteps(info.toString(), errors.toString());
+ for (UITestStep testStep : testSteps) {
+ if (!testStep.isSuccess()) {
+ String outDirPath = ACCEPTANCE_DIR + "out/";
+ File outDir = new File(outDirPath);
+ if (!outDir.exists()) {
+ boolean result = outDir.mkdirs();
+ if (!result)
+ throw new IOException("Couldn't create out directory");
+ }
+ captureScreen(outDirPath + getName() + ".png");
+ saveLog(testSteps, outDirPath + getName() + ".log");
+ break;
+ }
+ }
+
+ return testSteps;
+ }
+
+ private void saveLog(List testSteps, String logFile) throws Exception {
+ StringBuilder logBuilder = new StringBuilder();
+ for (UITestStep testStep : testSteps) {
+ if (testStep.isSuccess())
+ logBuilder.append("[ STEP ] ");
+ else
+ logBuilder.append("[ FAILED ] ");
+ logBuilder.append(testStep.getName()).append("\n");
+ for (UITestLogMessage logMessage : testStep.getLogMessages()) {
+ String level = "[ " + logMessage.getMessageLevel().toString() + " ] ";
+ logBuilder.append(level).append(logMessage.getMessage()).append("\n");
+
+ String content = logMessage.getContent().toString();
+ if (content.length() > 0)
+ logBuilder.append(content.trim()).append("\n");
+ }
+ }
+ FileUtils.writeStringToFile(new File(logFile), logBuilder.toString());
+ }
+
+ private void captureScreen(String destinationFile) throws Exception {
+ Robot robot = new Robot();
+ BufferedImage screenShot = robot.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
+ ImageIO.write(screenShot, "PNG", new File(destinationFile));
+ }
+
+ private void executeTestScript(String scriptFile, String testFileName,
+ StringBuilder errorLog, StringBuilder infoLog)
+ throws FileNotFoundException {
+ // get path to test script
+ String testFilePath = getTestFilePath(testFileName);
+ File testFile = new File(testFilePath);
+ if (!testFile.exists())
+ throw new FileNotFoundException("Couldn't found test file: " + testFile.getAbsolutePath());
+
+ try {
+ Process testRunner = Runtime.getRuntime().exec(scriptFile + " " + "\"" + testFilePath + "\"");
+
+ BufferedReader stdOut = new BufferedReader(new InputStreamReader(testRunner.getInputStream()));
+ BufferedReader stdError = new BufferedReader(new InputStreamReader(testRunner.getErrorStream()));
+
+ String s;
+ while ((s = stdOut.readLine()) != null) {
+ infoLog.append(s).append("\n");
+ }
+ while ((s = stdError.readLine()) != null) {
+ errorLog.append(s).append("\n");
+ }
+ } catch (IOException ex) {
+ throw new RuntimeException("Problem with execute external test");
+ }
+ }
+
+ private List analyzeTestSteps(String infoLog, String errorsLog) {
+ ArrayList uiTestSteps = new ArrayList();
+ UITestStep currentStep = new UITestStep("Init");
+ UITestLogMessage currentMessage = null;
+ String[] logStrings = infoLog.split("\n");
+ for (String logEnrty : logStrings) {
+ UITestLogMessage logMessage = UITestLogMessage.parse(logEnrty);
+ if (logMessage.getMessageLevel() == UITestLogMessage.Level.STEP) {
+ if (currentMessage != null)
+ currentStep.getLogMessages().add(currentMessage);
+ uiTestSteps.add(currentStep);
+ currentStep = new UITestStep(logMessage.getMessage());
+ currentMessage = null;
+ } else if (logMessage.getMessageLevel() != UITestLogMessage.Level.CONTENT) {
+ if (currentMessage != null)
+ currentStep.getLogMessages().add(currentMessage);
+ currentMessage = logMessage;
+ } else {
+ if (currentMessage == null)
+ currentMessage = new UITestLogMessage(UITestLogMessage.Level.CONTENT, "");
+ if (!StringUtils.isWhitespace(logMessage.getMessage()))
+ currentMessage.getContent().append(logMessage.getMessage().trim()).append("\n");
+ }
+ }
+
+ if (currentMessage != null)
+ currentStep.getLogMessages().add(currentMessage);
+ uiTestSteps.add(currentStep);
+
+ // mark failed steps
+ for (UITestStep step : uiTestSteps) {
+ for (UITestLogMessage logMessage : step.getLogMessages()) {
+ if (logMessage.getMessageLevel() == UITestLogMessage.Level.ERROR) {
+ step.setSuccess(false);
+ break;
+ }
+ }
+ }
+
+ if (!StringUtils.isWhitespace(errorsLog)) {
+ UITestLogMessage logMessage = new UITestLogMessage(UITestLogMessage.Level.ERROR, "Fatal error");
+ logMessage.getContent().append(errorsLog);
+
+ UITestStep lastStep = uiTestSteps.get(uiTestSteps.size() - 1);
+ lastStep.getLogMessages().add(logMessage);
+ lastStep.setSuccess(false);
+ }
+
+ return uiTestSteps;
+ }
+
+ protected String getTestExecutor() {
+ return "ui-test";
+ }
+
+ protected String getClientProgram() {
+ return "chrome";
+ }
+
+ private String getExecutorScript() {
+ String testExecutor = System.getenv(ACCEPTANCE_EXECUTOR_KEY);
+
+ if (StringUtils.isEmpty(testExecutor)) {
+ String scriptExt;
+ if (OsUtils.isWindows())
+ scriptExt = "bat";
+ else
+ scriptExt = ".sh";
+ testExecutor = getTestExecutor() + "." + scriptExt;
+ }
+ return testExecutor;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // start browser or another program
+ startProgramInstance();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ // Exit from client program
+ stopProgramInstance();
+ }
+
+ protected void startProgramInstance() throws IOException {
+ String clientProgram = System.getenv(ACCEPTANCE_CLIENT_KEY);
+ if (StringUtils.isEmpty(clientProgram))
+ clientProgram = getClientProgram();
+ clientRunner = Runtime.getRuntime().exec(clientProgram);
+ }
+
+ protected void stopProgramInstance() {
+ clientRunner.destroy();
+ }
+
+ protected String getTestFilePath(String testFileName) {
+ return ACCEPTANCE_DIR + "testsuite/" + testFileName;
+ }
+
+ protected boolean isSuccessful(List testSteps) {
+ boolean result = true;
+ for (UITestStep testStep : testSteps) {
+ result = result && testStep.isSuccess();
+ Assert.assertTrue("Failed on step: " + testStep.getName(), testStep.isSuccess());
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/modules/test-ui/src/com/haulmont/cuba/ui/UITestLogMessage.java b/modules/test-ui/src/com/haulmont/cuba/ui/UITestLogMessage.java
new file mode 100644
index 0000000000..8665a3f775
--- /dev/null
+++ b/modules/test-ui/src/com/haulmont/cuba/ui/UITestLogMessage.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
+ * Haulmont Technology proprietary and confidential.
+ * Use is subject to license terms.
+ */
+
+package com.haulmont.cuba.ui;
+
+import java.util.*;
+import java.util.regex.*;
+
+/**
+ * $Id$
+ *
+ * @author artamonov
+ */
+public class UITestLogMessage {
+
+ public static enum Level {
+ INFO,
+ DEBUG,
+ STEP,
+ ERROR,
+ LOG,
+ CONTENT
+ }
+
+ private Level messageLevel;
+ private StringBuilder content = new StringBuilder();
+ private String message;
+
+ private static Map logLevels = new HashMap();
+
+ static {
+ logLevels.put(Pattern.compile("(\\[info\\] )(.*)"), Level.INFO);
+ logLevels.put(Pattern.compile("(\\[log\\] )(.*)"), Level.LOG);
+ logLevels.put(Pattern.compile("(\\[step\\] )(.*)"), Level.STEP);
+ logLevels.put(Pattern.compile("(\\[debug\\] )(.*)"), Level.DEBUG);
+ logLevels.put(Pattern.compile("(\\[error\\] )(.*)"), Level.ERROR);
+ logLevels.put(Pattern.compile("(\\| )(.*)"), Level.CONTENT);
+ }
+
+ public static UITestLogMessage parse(String logEntry) {
+ boolean find = false;
+
+ String message = logEntry;
+ Level level = Level.CONTENT;
+
+ Iterator patternIterator = logLevels.keySet().iterator();
+ while (patternIterator.hasNext() && (!find)) {
+ Pattern pattern = patternIterator.next();
+ Matcher matcher = pattern.matcher(logEntry);
+ if (matcher.matches()) {
+ message = matcher.group(2);
+ level = logLevels.get(pattern);
+ find = true;
+ }
+ }
+ return new UITestLogMessage(level, message);
+ }
+
+ public UITestLogMessage(Level messageLevel, String message) {
+ this.messageLevel = messageLevel;
+ this.message = message;
+ }
+
+ public Level getMessageLevel() {
+ return messageLevel;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public StringBuilder getContent() {
+ return content;
+ }
+}
diff --git a/modules/test-ui/src/com/haulmont/cuba/ui/UITestStep.java b/modules/test-ui/src/com/haulmont/cuba/ui/UITestStep.java
new file mode 100644
index 0000000000..0a49381c6f
--- /dev/null
+++ b/modules/test-ui/src/com/haulmont/cuba/ui/UITestStep.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
+ * Haulmont Technology proprietary and confidential.
+ * Use is subject to license terms.
+ */
+
+package com.haulmont.cuba.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * $Id$
+ *
+ * @author artamonov
+ */
+public final class UITestStep {
+
+ private boolean success = true;
+ private String name;
+ private List logMessages = new ArrayList();
+
+ public UITestStep(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List getLogMessages() {
+ return logMessages;
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public void setSuccess(boolean success) {
+ this.success = success;
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 7254e83c51..5624162286 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -5,7 +5,7 @@
*/
include(':cuba-shared-lib', ':cuba-global', ':cuba-core', ':cuba-client', ':cuba-gui', ':cuba-web-toolkit',
- ':cuba-web', ':cuba-desktop')
+ ':cuba-web', ':cuba-desktop', ':cuba-test-ui')
rootProject.name = 'cuba'
project(':cuba-shared-lib').projectDir = new File(settingsDir, 'modules/shared-lib')
project(':cuba-global').projectDir = new File(settingsDir, 'modules/global')
@@ -15,3 +15,4 @@ project(':cuba-gui').projectDir = new File(settingsDir, 'modules/gui')
project(':cuba-web-toolkit').projectDir = new File(settingsDir, 'modules/web-toolkit')
project(':cuba-web').projectDir = new File(settingsDir, 'modules/web')
project(':cuba-desktop').projectDir = new File(settingsDir, 'modules/desktop')
+project(':cuba-test-ui').projectDir = new File(settingsDir, 'modules/test-ui')