Refs #817 Test UI Module

This commit is contained in:
Yuriy Artamonov 2011-07-11 12:18:07 +00:00
parent 8fc98ddf87
commit 418c984c47
6 changed files with 430 additions and 24 deletions

View File

@ -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 {

View File

@ -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;
/**
* <p>$Id$</p>
*
* @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");
}
}

View File

@ -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
* <p>$Id$</p>
*
* @author artamonov
*/
@SuppressWarnings("unused")
public abstract class UITestCase extends TestCase {
/**
* Key in system environment variables <br/>
* Define custom executor script
*/
private static final String ACCEPTANCE_EXECUTOR_KEY = "ACCEPTANCE_EXECUTOR";
/**
* Key in system environment variables <br/>
* 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 <br/>
* 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<UITestStep> runUiTest(String testFileName) throws Exception {
String executorScript = getExecutorScript();
StringBuilder errors = new StringBuilder();
StringBuilder info = new StringBuilder();
executeTestScript(executorScript, testFileName, errors, info);
List<UITestStep> 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<UITestStep> 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<UITestStep> analyzeTestSteps(String infoLog, String errorsLog) {
ArrayList<UITestStep> uiTestSteps = new ArrayList<UITestStep>();
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<UITestStep> testSteps) {
boolean result = true;
for (UITestStep testStep : testSteps) {
result = result && testStep.isSuccess();
Assert.assertTrue("Failed on step: " + testStep.getName(), testStep.isSuccess());
}
return result;
}
}

View File

@ -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.*;
/**
* <p>$Id$</p>
*
* @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<Pattern, Level> logLevels = new HashMap<Pattern, Level>();
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<Pattern> 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;
}
}

View File

@ -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;
/**
* <p>$Id$</p>
*
* @author artamonov
*/
public final class UITestStep {
private boolean success = true;
private String name;
private List<UITestLogMessage> logMessages = new ArrayList<UITestLogMessage>();
public UITestStep(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<UITestLogMessage> getLogMessages() {
return logMessages;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}

View File

@ -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')