diff --git a/example_test_suites/example_macos/case1/sakuli_demo.js b/example_test_suites/example_macos/case1/sakuli_demo.js index 41dab094..4103b676 100644 --- a/example_test_suites/example_macos/case1/sakuli_demo.js +++ b/example_test_suites/example_macos/case1/sakuli_demo.js @@ -32,17 +32,18 @@ try { testCase.endOfStep("Test Sahi landing page",30); appCalc.open(); - screen.waitForImage("calculator.png", 10); +// screen.waitForImage("calculator.png", 10); + env.sleep(4); env.type("525"); env.sleep(2); screen.find("plus.png").click().type("100"); screen.find("result.png").click(); - screen.waitForImage("625", 5); +// screen.waitForImage("625", 5); testCase.endOfStep("Calculation",30); appGedit.open(); - screen.waitForImage("gedit.png", 10); +// screen.waitForImage("gedit.png", 5); env.paste("Initial test passed. Sakuli, Sahi and Sikuli seem to work fine. Exiting..."); testCase.endOfStep("Editor",30); env.sleep(4); diff --git a/pom.xml b/pom.xml index 89752a50..fe592213 100644 --- a/pom.xml +++ b/pom.xml @@ -610,6 +610,11 @@ jtwig-spaceless-extension 1.44 + + com.google.code.gson + gson + 2.8.1 + diff --git a/src/common/pom.xml b/src/common/pom.xml index 9588e729..a8816637 100644 --- a/src/common/pom.xml +++ b/src/common/pom.xml @@ -41,6 +41,7 @@ false **/version.txt + **/sakuli-default.properties @@ -48,6 +49,7 @@ true **/version.txt + **/sakuli-default.properties diff --git a/src/common/src/main/resources/org/sakuli/common/bin/installer_scripts/linux/set_sakuli_home.sh b/src/common/src/main/resources/org/sakuli/common/bin/installer_scripts/linux/set_sakuli_home.sh index 1d194c79..fc72e569 100755 --- a/src/common/src/main/resources/org/sakuli/common/bin/installer_scripts/linux/set_sakuli_home.sh +++ b/src/common/src/main/resources/org/sakuli/common/bin/installer_scripts/linux/set_sakuli_home.sh @@ -28,6 +28,21 @@ function set_sakuli_root { fi } +function remove_sakuli_root { + echo "remove 'SAKULI_HOME' from '.bashrc'" + [ -r ~/.bashrc ] && sed -i '/export SAKULI_HOME/d' ~/.bashrc +} + +function set_sakuli_root { + if [ -d "$1" ]; then + echo "set 'SAKULI_ROOT' in '.bashrc' to: $1" + echo "export SAKULI_ROOT=\"$1\"" >> ~/.bashrc + else + echo "new 'SAKULI_ROOT' path '$1' does not exists!" + exit 1 + fi +} + function set_path { echo "$PATH" if grep -q SAKULI_HOME/bin: ~/.bashrc 2>/dev/null; then diff --git a/src/common/src/main/resources/org/sakuli/common/config/sakuli-default.properties b/src/common/src/main/resources/org/sakuli/common/config/sakuli-default.properties index c04b5e81..a418cc9a 100644 --- a/src/common/src/main/resources/org/sakuli/common/config/sakuli-default.properties +++ b/src/common/src/main/resources/org/sakuli/common/config/sakuli-default.properties @@ -280,6 +280,16 @@ sakuli.forwarder.check_mk.spoolfile_prefix=sakuli_suite_ # optional service description forwarded to the output check result, when not set, testsuite.id is used sakuli.forwarder.check_mk.service_description=${testsuite.id} + +##### JSON - FORWARDER +# Save results into a json file. The JSON forwarder is enabled per default. + +# DEFAULT: true +sakuli.forwarder.json.enabled=true +## database host +sakuli.forwarder.json.dir=${sakuli.log.folder}/_json + + ############################### # LOGGING & ERROR-SCREENSHOT PROPERTIES # Common logging settings for Sakuli. @@ -434,3 +444,10 @@ java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.FileHandler.limit=102400 java.util.logging.FileHandler.count=10 java.util.logging.FileHandler.pattern=%t/sahi%g.log + +#TODO ${project.version} +sakuli.version=1.0.2 + +###URLs for documentation links +sahi.doc.url=http://sahipro.com/docs/sahi-apis/action-apis.html +sakuli.doc.base.url=http://consol.github.io/sakuli/v${sakuli.version}/index.html# diff --git a/src/core/pom.xml b/src/core/pom.xml index 1b3dfe4c..ecadd604 100644 --- a/src/core/pom.xml +++ b/src/core/pom.xml @@ -448,6 +448,11 @@ org.jtwig jtwig-spaceless-extension + + + com.google.code.gson + gson + diff --git a/src/core/src/main/java/org/sakuli/aop/RhinoAspect.java b/src/core/src/main/java/org/sakuli/aop/RhinoAspect.java index 44c2f77f..c15ea9c6 100644 --- a/src/core/src/main/java/org/sakuli/aop/RhinoAspect.java +++ b/src/core/src/main/java/org/sakuli/aop/RhinoAspect.java @@ -27,12 +27,19 @@ import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.sakuli.actions.logging.LogToResult; +import org.sakuli.datamodel.TestAction; +import org.sakuli.datamodel.TestCaseStep; import org.sakuli.datamodel.actions.LogResult; -import org.sakuli.loader.*; +import org.sakuli.loader.BaseActionLoader; +import org.sakuli.loader.BaseActionLoaderImpl; +import org.sakuli.loader.BeanLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; + import static org.apache.commons.lang.StringUtils.isNotEmpty; import static org.apache.commons.lang.StringUtils.removeEnd; import static org.apache.commons.lang3.StringUtils.removeStart; @@ -118,6 +125,8 @@ protected void addActionLog(JoinPoint joinPoint, LogToResult logToResult) { if (logToResult != null) { StringBuilder message = createLoggingString(joinPoint, logToResult); + addActionsToCurrentStep(extractTestAction(joinPoint, logToResult)); + //log the action to log file and print switch (logToResult.level()) { case ERROR: @@ -147,6 +156,34 @@ protected void addActionLog(JoinPoint joinPoint, LogToResult logToResult) { } } + //TODO evtl. move the test action creation to a new service + private List cachedTestActions = new ArrayList<>(); + + protected void addActionsToCurrentStep(TestAction currentTestAction) { + if (currentTestAction != null) { + cachedTestActions.add(currentTestAction); + } + TestCaseStep currentTestCaseStep = BeanLoader.loadBaseActionLoader().getCurrentTestCaseStep(); + if (currentTestCaseStep != null) { + currentTestCaseStep.addActions(cachedTestActions); + //reset the list of actions, since those actions have already been added to the current test step. + cachedTestActions = new ArrayList<>(); + } + } + + protected TestAction extractTestAction(JoinPoint joinPoint, LogToResult logToResult) { + if (logToResult.logArgsOnly()) { + return null; + } + return TestAction.createSakuliTestAction( + joinPoint.getSignature().getDeclaringType().getSimpleName(), + joinPoint.getSignature().getName(), + joinPoint.getArgs(), + logToResult.message(), + BeanLoader.loadBaseActionLoader().getSakuliProperties().getSakuliDocBaseUrl() + ); + } + /** * @return based on the different arguments of the {@link LogToResult} annotation an different output {@link String} */ @@ -213,6 +250,7 @@ public void doHandleRhinoException(JoinPoint joinPoint) { else if (logResult.getDebugInfo() == null || !logResult.getDebugInfo().startsWith("org.sakuli.actions.")) { logger.info(logResult.getMessage()); + addActionsToCurrentStep(TestAction.createSahiTestAction(logResult.getMessage(), BeanLoader.loadBaseActionLoader().getSakuliProperties().getSahiDocUrl())); } } diff --git a/src/core/src/main/java/org/sakuli/datamodel/TestAction.java b/src/core/src/main/java/org/sakuli/datamodel/TestAction.java new file mode 100644 index 00000000..5ed1c323 --- /dev/null +++ b/src/core/src/main/java/org/sakuli/datamodel/TestAction.java @@ -0,0 +1,72 @@ +/* + * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. + * + * Copyright 2013 - 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakuli.datamodel; + +/** + * Created by georgi on 21/09/17. + */ +public class TestAction { + private String object; + private String method; + private Object[] args; + private String message; + private String documentationURL; + + private TestAction(String object, String method, Object[] args, String message, String documentationURL) { + this.object = object; + this.method = method; + this.args = args; + this.message = message; + this.documentationURL = documentationURL; + } + + public static TestAction createSakuliTestAction(String object, String method, Object[] args, String message, String documentationURL) { + return new TestAction(object, method, args, message, + createDocumentationURL(documentationURL, object, method)); + } + + public static TestAction createSahiTestAction(String method, String documentationURL) { + return new TestAction(method, method, null, null, documentationURL); + } + + private static String createDocumentationURL(String baseUrl, String clazz, String method) { + return baseUrl + clazz + "." + method; + } + + public String getMessage() { + return message; + } + + public Object[] getArgs() { + return args; + } + + public String getMethod() { + return method; + } + + public Object getObject() { + return object; + } + + public String getDocumentationURL() { + return documentationURL; + } + +} diff --git a/src/core/src/main/java/org/sakuli/datamodel/TestCaseStep.java b/src/core/src/main/java/org/sakuli/datamodel/TestCaseStep.java index bceb410f..7b7e773e 100644 --- a/src/core/src/main/java/org/sakuli/datamodel/TestCaseStep.java +++ b/src/core/src/main/java/org/sakuli/datamodel/TestCaseStep.java @@ -19,10 +19,12 @@ package org.sakuli.datamodel; import org.apache.commons.lang.StringUtils; -import org.sakuli.datamodel.properties.SakuliProperties; import org.sakuli.datamodel.state.TestCaseStepState; import org.sakuli.exceptions.SakuliException; +import java.util.ArrayList; +import java.util.List; + /** * test case step based Exceptions and critical times will be currently not supported in {@link * org.sakuli.actions.TestCaseAction}. @@ -31,6 +33,8 @@ */ public class TestCaseStep extends AbstractTestDataEntity { + private List testActions = new ArrayList<>(); + /** * {@inheritDoc} */ @@ -85,4 +89,9 @@ public void setName(String name) { public void setId(String id) { this.setName(id); } + + public void addActions(List testActions) { + this.testActions.addAll(testActions); + } + } diff --git a/src/core/src/main/java/org/sakuli/datamodel/properties/ForwarderProperties.java b/src/core/src/main/java/org/sakuli/datamodel/properties/ForwarderProperties.java index 311e406a..e4ff12ee 100644 --- a/src/core/src/main/java/org/sakuli/datamodel/properties/ForwarderProperties.java +++ b/src/core/src/main/java/org/sakuli/datamodel/properties/ForwarderProperties.java @@ -31,10 +31,12 @@ public class ForwarderProperties extends AbstractProperties { public static final String GEARMAN_ENABLED = "sakuli.forwarder.gearman.enabled"; public static final String ICINGA_ENABLED = "sakuli.forwarder.icinga2.enabled"; public static final String CHECK_MK_ENABLED = "sakuli.forwarder.check_mk.enabled"; + public static final String JSON_ENABLED = "sakuli.forwarder.json.enabled"; protected static final boolean DATABASE_ENABLED_DEFAULT = false; protected static final boolean GEARMAN_ENABLED_DEFAULT = false; protected static final boolean ICINGA_ENABLED_DEFAULT = false; protected static final boolean CHECK_MK_ENABLED_DEFAULT = false; + protected static final boolean JSON_ENABLED_DEFAULT = true; @Value("${" + DATABASE_ENABLED + ":" + DATABASE_ENABLED_DEFAULT + "}") private boolean databaseEnabled; @@ -45,6 +47,9 @@ public class ForwarderProperties extends AbstractProperties { @Value("${" + CHECK_MK_ENABLED + ":" + CHECK_MK_ENABLED_DEFAULT + "}") private boolean checkMKEnabled; + @Value("${" + JSON_ENABLED + ":" + JSON_ENABLED_DEFAULT + "}") + private boolean jsonEnabled; + public boolean isDatabaseEnabled() { return databaseEnabled; } @@ -73,4 +78,12 @@ public boolean isCheckMKEnabled() { return checkMKEnabled; } + public void setJsonEnabled(boolean jsonEnabled) { + this.jsonEnabled = jsonEnabled; + } + + public boolean isJsonEnabled() { + return jsonEnabled; + } + } diff --git a/src/core/src/main/java/org/sakuli/datamodel/properties/SakuliProperties.java b/src/core/src/main/java/org/sakuli/datamodel/properties/SakuliProperties.java index a1bd7984..4e44d569 100644 --- a/src/core/src/main/java/org/sakuli/datamodel/properties/SakuliProperties.java +++ b/src/core/src/main/java/org/sakuli/datamodel/properties/SakuliProperties.java @@ -48,10 +48,12 @@ public class SakuliProperties extends AbstractProperties { public static final String LOG_LEVEL_SPRING = "log.level.spring"; public static final String LOG_LEVEL_ROOT = "log.level.root"; public static final String FORWARDER_TEMPLATE_FOLDER = "sakuli.forwarder.template.folder"; + public static final String SAHI_DOC_URL = "sahi.doc.url"; + public static final String SAKULI_DOC_BASE_URL = "sakuli.doc.base.url"; public static final String CONFIG_FOLDER_APPEDER = File.separator + "config"; public static final String LIBS_FOLDER_APPEDER = File.separator + "libs"; public static final String JS_LIB_FOLDER_APPEDER = LIBS_FOLDER_APPEDER + File.separator + "js"; - // have to be the common/libs older, so that {@link TextRecognizer} can add "tessdata" to the path! + // have to be the common/libs folder, so that {@link TextRecognizer} can add "tessdata" to the path! public static final String TESSDATA_LIB_FOLDER_APPEDER = LIBS_FOLDER_APPEDER; private static final boolean SUPPRESS_RESUMED_EXCEPTIONS_DEFAULT = false; private static final boolean JAVASCRIPT_ENGINE_DEFAULT = true; @@ -84,6 +86,11 @@ public class SakuliProperties extends AbstractProperties { private String logLevelRoot; @Value("${" + FORWARDER_TEMPLATE_FOLDER + ":}") private String forwarderTemplateFolder; + @Value("${" + SAHI_DOC_URL + "}") + private String sahiDocumentationUrl; + @Value("${" + SAKULI_DOC_BASE_URL + "}") + private String sakuliDocumentationBaseUrl; + private Path configFolder; private Path jsLibFolder; private Path tessDataLibFolder; @@ -241,4 +248,12 @@ public String getForwarderTemplateFolder() { return forwarderTemplateFolder; } + public String getSahiDocUrl() { + return sahiDocumentationUrl; + } + + public String getSakuliDocBaseUrl() { + return sakuliDocumentationBaseUrl; + } + } diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/AbstractOutputBuilder.java b/src/core/src/main/java/org/sakuli/services/forwarder/AbstractOutputBuilder.java index 9d19b62d..b57aec55 100644 --- a/src/core/src/main/java/org/sakuli/services/forwarder/AbstractOutputBuilder.java +++ b/src/core/src/main/java/org/sakuli/services/forwarder/AbstractOutputBuilder.java @@ -44,7 +44,7 @@ * @author tschneck * Date: 2/24/16 */ -public abstract class AbstractOutputBuilder { +public abstract class AbstractOutputBuilder { public final static SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.YY HH:mm:ss"); protected Logger LOGGER = LoggerFactory.getLogger(getClass()); diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/ScreenshotDivConverter.java b/src/core/src/main/java/org/sakuli/services/forwarder/ScreenshotDivConverter.java index 84b5f21b..969d3564 100644 --- a/src/core/src/main/java/org/sakuli/services/forwarder/ScreenshotDivConverter.java +++ b/src/core/src/main/java/org/sakuli/services/forwarder/ScreenshotDivConverter.java @@ -26,6 +26,7 @@ import org.sakuli.services.forwarder.gearman.ProfileGearman; import org.sakuli.services.forwarder.gearman.model.ScreenshotDiv; import org.sakuli.services.forwarder.icinga2.ProfileIcinga2; +import org.sakuli.services.forwarder.json.ProfileJson; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import sun.misc.BASE64Encoder; @@ -41,6 +42,7 @@ @ProfileGearman @ProfileIcinga2 @ProfileCheckMK +@ProfileJson @Component public class ScreenshotDivConverter { diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/json/GsonExclusionStrategy.java b/src/core/src/main/java/org/sakuli/services/forwarder/json/GsonExclusionStrategy.java new file mode 100644 index 00000000..4641357a --- /dev/null +++ b/src/core/src/main/java/org/sakuli/services/forwarder/json/GsonExclusionStrategy.java @@ -0,0 +1,43 @@ +/* + * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. + * + * Copyright 2013 - 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakuli.services.forwarder.json; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import org.sikuli.script.Region; +import org.slf4j.Logger; + +/** + * Created by georgi on 27/09/17. + */ +public class GsonExclusionStrategy implements ExclusionStrategy { + + @Override + public boolean shouldSkipClass(Class c) { + return Logger.class.isAssignableFrom(c) + || Region.class.isAssignableFrom(c) + ; + } + + @Override + public boolean shouldSkipField(FieldAttributes f) { + return false; + } + +} diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/json/GsonOutputBuilder.java b/src/core/src/main/java/org/sakuli/services/forwarder/json/GsonOutputBuilder.java new file mode 100644 index 00000000..9098ba8c --- /dev/null +++ b/src/core/src/main/java/org/sakuli/services/forwarder/json/GsonOutputBuilder.java @@ -0,0 +1,72 @@ +/* + * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. + * + * Copyright 2013 - 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakuli.services.forwarder.json; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.joda.time.DateTime; +import org.sakuli.exceptions.SakuliForwarderException; +import org.sakuli.services.forwarder.AbstractOutputBuilder; +import org.sakuli.services.forwarder.json.serializer.DateTimeSerializer; +import org.sakuli.services.forwarder.json.serializer.PathSerializer; +import org.sakuli.services.forwarder.json.serializer.ThrowableSerializer; +import org.springframework.stereotype.Component; + +import java.nio.file.Path; + +/** + * Created by georgi on 27/09/17. + */ +@ProfileJson +@Component +public class GsonOutputBuilder extends AbstractOutputBuilder { + + /** + * Converts the current test suite object to a json string. + * + * @return + */ + public String createOutput() throws SakuliForwarderException { + try { + Gson gsonBuilder = new GsonBuilder() + .setExclusionStrategies(new GsonExclusionStrategy()) + .registerTypeAdapter(DateTime.class, new DateTimeSerializer()) + .registerTypeAdapter(Path.class, new PathSerializer()) + .registerTypeHierarchyAdapter(Throwable.class, new ThrowableSerializer()) + .serializeNulls() + .create(); + return gsonBuilder.toJson(testSuite); + } catch (Throwable thr) { + throw new SakuliForwarderException(thr, "Exception during serializing testSuite into JSON!"); + } + } + + @Override + protected int getSummaryMaxLength() { + // operation is not used for the gson output builder + return 0; + } + + @Override + protected String getOutputScreenshotDivWidth() { + // operation is not used for the gson output builder + return null; + } + +} diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/json/JsonProperties.java b/src/core/src/main/java/org/sakuli/services/forwarder/json/JsonProperties.java new file mode 100644 index 00000000..313db818 --- /dev/null +++ b/src/core/src/main/java/org/sakuli/services/forwarder/json/JsonProperties.java @@ -0,0 +1,39 @@ +/* + * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. + * + * Copyright 2013 - 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakuli.services.forwarder.json; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Created by georgi on 27/09/17. + */ +@ProfileJson +@Component +public class JsonProperties { + private static final String OUTPUT_DIR = "sakuli.forwarder.json.dir"; + + @Value("${" + OUTPUT_DIR + "}") + private String outputJsonDir; + + public String getOutputJsonDir() { + return outputJsonDir; + } + +} diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/json/JsonResultServiceImpl.java b/src/core/src/main/java/org/sakuli/services/forwarder/json/JsonResultServiceImpl.java new file mode 100644 index 00000000..1eb83886 --- /dev/null +++ b/src/core/src/main/java/org/sakuli/services/forwarder/json/JsonResultServiceImpl.java @@ -0,0 +1,100 @@ +/* + * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. + * + * Copyright 2013 - 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakuli.services.forwarder.json; + +import org.sakuli.exceptions.SakuliForwarderException; +import org.sakuli.services.common.AbstractResultService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author Georgi Todorov + */ +@ProfileJson +@Component +public class JsonResultServiceImpl extends AbstractResultService { + + private static final Logger logger = LoggerFactory.getLogger(JsonResultServiceImpl.class); + + @Autowired + private GsonOutputBuilder outputBuilder; + + @Autowired + private JsonProperties jsonProperties; + + @Override + public int getServicePriority() { + return 10; + } + + @Override + public void saveAllResults() { + try { + logger.info("======= WRITE TEST OUTPUT AS JSON FILE ======"); + String output = outputBuilder.createOutput(); + logger.debug(String.format("JSON Output:\n%s", output)); + writeToFile(createJsonFilePath(), output); + logger.info("======= FINISHED: WRITE TEST OUTPUT AS JSON FILE ======"); + } catch (SakuliForwarderException e) { + exceptionHandler.handleException(e, false); + } + } + + protected Path createJsonFilePath() throws SakuliForwarderException { + String outputDirAsString = jsonProperties.getOutputJsonDir(); + Path outputDir = Paths.get(outputDirAsString); + if (!Files.exists(outputDir)) { + try { + Files.createDirectories(outputDir); + } catch (IOException e) { + throw new SakuliForwarderException(e, + String.format("Unexpected error during creating the json output directory '%s'", outputDirAsString)); + } + } + String fileName = new StringBuilder() + .append(testSuite.getId()) + .append("_") + .append(new SimpleDateFormat("yyyy.MM.dd-HH:mm:ss.SSS").format(new Date())) + .append(".json") + .toString(); + return Paths.get(outputDirAsString + File.separator + fileName); + } + + private void writeToFile(Path file, String output) throws SakuliForwarderException { + try { + logger.info(String.format("Write file to '%s'", file)); + Files.write(file, output.getBytes(), StandardOpenOption.CREATE); + } catch (IOException e) { + throw new SakuliForwarderException(e, + String.format("Unexpected error by writing the json output to the following file '%s'", file)); + } + } + +} diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/json/ProfileJson.java b/src/core/src/main/java/org/sakuli/services/forwarder/json/ProfileJson.java new file mode 100644 index 00000000..77c5b15f --- /dev/null +++ b/src/core/src/main/java/org/sakuli/services/forwarder/json/ProfileJson.java @@ -0,0 +1,29 @@ +/* + * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. + * + * Copyright 2013 - 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakuli.services.forwarder.json; + +import org.sakuli.utils.SpringProfilesInitializer; +import org.springframework.context.annotation.Profile; + +/** + * Created by georgi on 27/09/17. + */ +@Profile(SpringProfilesInitializer.JSON) +public @interface ProfileJson { +} diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/json/serializer/DateTimeSerializer.java b/src/core/src/main/java/org/sakuli/services/forwarder/json/serializer/DateTimeSerializer.java new file mode 100644 index 00000000..b37cd219 --- /dev/null +++ b/src/core/src/main/java/org/sakuli/services/forwarder/json/serializer/DateTimeSerializer.java @@ -0,0 +1,43 @@ +/* + * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. + * + * Copyright 2013 - 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakuli.services.forwarder.json.serializer; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.apache.commons.lang.StringUtils; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.ISODateTimeFormat; + +import java.lang.reflect.Type; + +/** + * Created by georgi on 27/09/17. + */ +public class DateTimeSerializer implements JsonSerializer { + final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC); + + @Override + public JsonElement serialize(final DateTime src, final Type typeOfSrc, final JsonSerializationContext context) { + return new JsonPrimitive(src == null ? StringUtils.EMPTY : DATE_TIME_FORMATTER.print(src)); + } +} diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/json/serializer/PathSerializer.java b/src/core/src/main/java/org/sakuli/services/forwarder/json/serializer/PathSerializer.java new file mode 100644 index 00000000..aa8e81e4 --- /dev/null +++ b/src/core/src/main/java/org/sakuli/services/forwarder/json/serializer/PathSerializer.java @@ -0,0 +1,38 @@ +/* + * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. + * + * Copyright 2013 - 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakuli.services.forwarder.json.serializer; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.apache.commons.lang.StringUtils; + +import java.lang.reflect.Type; +import java.nio.file.Path; + +/** + * Created by georgi on 27/09/17. + */ +public final class PathSerializer implements JsonSerializer { + @Override + public JsonElement serialize(final Path src, final Type typeOfSrc, final JsonSerializationContext context) { + return new JsonPrimitive(src == null ? StringUtils.EMPTY : src.toString()); + } +} diff --git a/src/core/src/main/java/org/sakuli/services/forwarder/json/serializer/ThrowableSerializer.java b/src/core/src/main/java/org/sakuli/services/forwarder/json/serializer/ThrowableSerializer.java new file mode 100644 index 00000000..821d4c0c --- /dev/null +++ b/src/core/src/main/java/org/sakuli/services/forwarder/json/serializer/ThrowableSerializer.java @@ -0,0 +1,52 @@ +/* + * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. + * + * Copyright 2013 - 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakuli.services.forwarder.json.serializer; + +import com.google.gson.*; +import org.apache.commons.lang.StringUtils; +import org.sakuli.exceptions.SakuliExceptionWithScreenshot; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Type; + +/** + * Created by georgi on 27/09/17. + */ +public class ThrowableSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(final Throwable src, final Type typeOfSrc, final JsonSerializationContext context) { + + if (src == null) { + return new JsonPrimitive(StringUtils.EMPTY); + } + JsonObject obj = new JsonObject(); + StringWriter sw = new StringWriter(); + src.printStackTrace(new PrintWriter(sw)); + obj.addProperty("stackTrace", sw.toString()); + obj.addProperty("detailMessage", src.getMessage()); + if (SakuliExceptionWithScreenshot.class.isAssignableFrom(src.getClass())) { + obj.addProperty("screenshot", ((SakuliExceptionWithScreenshot)src).getScreenshot().toString()); + } + + return obj; + } + +} diff --git a/src/core/src/main/java/org/sakuli/utils/SpringProfilesInitializer.java b/src/core/src/main/java/org/sakuli/utils/SpringProfilesInitializer.java index a09168e5..5ed163c6 100644 --- a/src/core/src/main/java/org/sakuli/utils/SpringProfilesInitializer.java +++ b/src/core/src/main/java/org/sakuli/utils/SpringProfilesInitializer.java @@ -45,6 +45,7 @@ public class SpringProfilesInitializer implements InitializingBean, ApplicationC public static final String GEARMAN = "GEARMAN"; public static final String INCINGA2 = "ICINGA2"; public static final String CHECK_MK = "CHECK_MK"; + public static final String JSON = "JSON"; public static final String CIPHER_INTERFACE = "CIPHER_INTERFACE"; public static final String CIPHER_ENV = "CIPHER_ENV"; @@ -85,6 +86,9 @@ protected String[] getConfiguredProfiles() { if (forwarderProperties.isCheckMKEnabled()) { profileNames.add(CHECK_MK); } + if (forwarderProperties.isJsonEnabled()) { + profileNames.add(JSON); + } //cipher profiles if (cipherProperties.isEncryptionModeEnv()) { diff --git a/test2.json b/test2.json new file mode 100644 index 00000000..103062d8 --- /dev/null +++ b/test2.json @@ -0,0 +1,264 @@ +{ + "browserName": "firefox", + "browserInfo": "Mozilla/5.0 (Macintosh, Intel Mac OS X 10.10, rv:49.0) Gecko/20100101 Firefox/49.0", + "host": "MacBookProConsol.fritz.box", + "testSuiteFolder": "/Users/georgi/Projects/testautomatisierung/sakuli/example_test_suites/example_macos", + "testSuiteFile": "/Users/georgi/Projects/testautomatisierung/sakuli/example_test_suites/example_macos/testsuite.suite", + "dbJobPrimaryKey": -1, + "testCases": { + "case1": { + "startUrl": "http://sahi.example.com/_s_/dyn/Driver_initialized", + "lastURL": "http://sahi.example.com/_s_/dyn/Driver_initialized", + "steps": [ + { + "testActions": [ + { + "object": "TestCaseAction", + "method": "getIdFromPath", + "args": [ + "/Users/georgi/Projects/testautomatisierung/sakuli/example_test_suites/example_macos/case1/undefined" + ], + "message": "convert the path of the test case file to a valid test case ID", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#TestCaseAction.getIdFromPath" + }, + { + "object": "TestCaseAction", + "method": "init", + "args": [ + "case1", + 60, + 70, + [ + "/Users/georgi/Projects/testautomatisierung/sakuli/example_test_suites/example_macos/case1" + ] + ], + "message": "init a new test case", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#TestCaseAction.init" + }, + { + "object": "_highlight(_link(\"SSL Manager\"));", + "method": "_highlight(_link(\"SSL Manager\"));", + "args": null, + "message": null, + "documentationURL": "http://sahipro.com/docs/sahi-apis/action-apis.html" + }, + { + "object": "_highlight(_link(\"Logs\"));", + "method": "_highlight(_link(\"Logs\"));", + "args": null, + "message": null, + "documentationURL": "http://sahipro.com/docs/sahi-apis/action-apis.html" + }, + { + "object": "_highlight(_link(\"Online Documentation\"));", + "method": "_highlight(_link(\"Online Documentation\"));", + "args": null, + "message": null, + "documentationURL": "http://sahipro.com/docs/sahi-apis/action-apis.html" + }, + { + "object": "_highlight(_link(\"Test Pages\"));", + "method": "_highlight(_link(\"Test Pages\"));", + "args": null, + "message": null, + "documentationURL": "http://sahipro.com/docs/sahi-apis/action-apis.html" + }, + { + "object": "_highlight(_link(\"Sample Application\"));", + "method": "_highlight(_link(\"Sample Application\"));", + "args": null, + "message": null, + "documentationURL": "http://sahipro.com/docs/sahi-apis/action-apis.html" + }, + { + "object": "TestCaseAction", + "method": "addTestCaseStep", + "args": [ + "Test Sahi landing page", + "1506528264044", + "1506528265467", + 30 + ], + "message": "add a step to the current test case", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#TestCaseAction.addTestCaseStep" + } + ], + "startDate": "Sep 27, 2017 6:04:24 PM", + "stopDate": "Sep 27, 2017 6:04:25 PM", + "exception": null, + "state": "OK", + "name": "Test_Sahi_landing_page", + "dbPrimaryKey": -1, + "warningTime": 30, + "criticalTime": -1, + "id": "Test_Sahi_landing_page", + "creationDate": "2017-09-27T16:04:19.472Z" + }, + { + "testActions": [ + { + "object": "Application", + "method": "open", + "args": [], + "message": "open application", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Application.open" + }, + { + "object": "Environment", + "method": "sleep", + "args": [ + 4.0 + ], + "message": "sleep and do nothing for x seconds", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Environment.sleep" + }, + { + "object": "Environment", + "method": "type", + "args": [ + "525" + ], + "message": "type over system keyboard", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Environment.type" + }, + { + "object": "Environment", + "method": "sleep", + "args": [ + 2.0 + ], + "message": "sleep and do nothing for x seconds", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Environment.sleep" + }, + { + "object": "Region", + "method": "find", + "args": [ + "plus.png" + ], + "message": "find the image in this region", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Region.find" + }, + { + "object": "Region", + "method": "click", + "args": [], + "message": "", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Region.click" + }, + { + "object": "Region", + "method": "type", + "args": [ + "100" + ], + "message": "type over system keyboard", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Region.type" + }, + { + "object": "Region", + "method": "find", + "args": [ + "result.png" + ], + "message": "find the image in this region", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Region.find" + }, + { + "object": "Region", + "method": "click", + "args": [], + "message": "", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Region.click" + }, + { + "object": "TestCaseAction", + "method": "addTestCaseStep", + "args": [ + "Calculation", + "1506528265467", + "1506528276537", + 30 + ], + "message": "add a step to the current test case", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#TestCaseAction.addTestCaseStep" + } + ], + "startDate": "Sep 27, 2017 6:04:25 PM", + "stopDate": "Sep 27, 2017 6:04:36 PM", + "exception": null, + "state": "OK", + "name": "Calculation", + "dbPrimaryKey": -1, + "warningTime": 30, + "criticalTime": -1, + "id": "Calculation", + "creationDate": "2017-09-27T16:04:19.473Z" + }, + { + "testActions": [ + { + "object": "Application", + "method": "open", + "args": [], + "message": "open application", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Application.open" + }, + { + "object": "Environment", + "method": "paste", + "args": [ + "Initial test passed. Sakuli, Sahi and Sikuli seem to work fine. Exiting..." + ], + "message": "", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#Environment.paste" + }, + { + "object": "TestCaseAction", + "method": "addTestCaseStep", + "args": [ + "Editor", + "1506528276537", + "1506528278204", + 30 + ], + "message": "add a step to the current test case", + "documentationURL": "http://consol.github.io/sakuli/v1.0.2/index.html#TestCaseAction.addTestCaseStep" + } + ], + "startDate": "Sep 27, 2017 6:04:36 PM", + "stopDate": "Sep 27, 2017 6:04:38 PM", + "exception": null, + "state": "OK", + "name": "Editor", + "dbPrimaryKey": -1, + "warningTime": 30, + "criticalTime": -1, + "id": "Editor", + "creationDate": "2017-09-27T16:04:19.474Z" + } + ], + "tcFile": "/Users/georgi/Projects/testautomatisierung/sakuli/example_test_suites/example_macos/case1/sakuli_demo.js", + "startDate": "Sep 27, 2017 6:04:24 PM", + "stopDate": "Sep 27, 2017 6:04:42 PM", + "exception": null, + "state": "OK", + "name": "case1", + "dbPrimaryKey": -1, + "warningTime": 60, + "criticalTime": 70, + "id": "case1", + "creationDate": "2017-09-27T16:04:19.469Z" + } + }, + "startDate": "Sep 27, 2017 6:04:19 PM", + "stopDate": "Sep 27, 2017 6:04:46 PM", + "exception": null, + "state": "OK", + "name": "example test suite for Sakuli", + "dbPrimaryKey": -1, + "warningTime": 120, + "criticalTime": 140, + "id": "example_macos", + "creationDate": "2017-09-27T16:04:19.279Z" +} \ No newline at end of file