jms -> mq

This commit is contained in:
Radek Davidek 2026-04-20 10:17:56 +02:00
parent 70c4540c4a
commit ab7d898b8e
5554 changed files with 439957 additions and 439459 deletions

View File

@ -1,6 +1,6 @@
.idea
target
*.iml
.classpath
.project
.settings
.idea
target
*.iml
.classpath
.project
.settings

View File

@ -1,3 +1,3 @@
# Test Harness
This repo conatins Test Harness, code name "Hercules".
# Test Harness
This repo conatins Test Harness, code name "Hercules".

View File

@ -30,7 +30,6 @@
<commons-configuration.version>1.6</commons-configuration.version>
<cxf.version>4.0.3</cxf.version>
<ibm.mq.version>9.4.5.0</ibm.mq.version>
<javax.jms.version>2.0.1</javax.jms.version>
<kafka.clients.version>3.7.0</kafka.clients.version>
<confluent.version>7.6.0</confluent.version>
<assertj.version>3.24.2</assertj.version>
@ -296,12 +295,6 @@
<artifactId>com.ibm.mq.allclient</artifactId>
<version>${ibm.mq.version}</version>
</dependency>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>${javax.jms.version}</version>
</dependency>
<!-- Kafka dependencies -->
<dependency>
<groupId>org.apache.kafka</groupId>

View File

@ -1,418 +1,418 @@
package cz.moneta.test.harness;
import cz.moneta.test.harness.annotations.*;
import cz.moneta.test.harness.config.ConfigProvider;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.BaseStoreAccessor;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.endpoints.MobileEndpoint;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import cz.moneta.test.harness.endpoints.greenscreen.GreenScreenEndpoint;
import cz.moneta.test.harness.endpoints.jira.JiraTestResultPublisher;
import cz.moneta.test.harness.exception.BeforeAllHarnessException;
import cz.moneta.test.harness.exception.HarnessConfigurationException;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.auth.AuthSupport;
import cz.moneta.test.harness.support.auth.Credentials;
import cz.moneta.test.harness.support.auth.Key;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.*;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.platform.engine.UniqueId;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class HarnessJunit5Extension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver,
TestExecutionExceptionHandler, AfterEachCallback, ExecutionCondition, LifecycleMethodExecutionExceptionHandler,
BeforeTestExecutionCallback, AfterAllCallback {
private static final Logger logger = LogManager.getLogger(HarnessJunit5Extension.class);
public static final String ACTIVE_ENDPOINTS = "ACTIVE_ENDPOINTS";
private static final Namespace ENDPOINT_NAMESPACE = Namespace.create("ENDPOINT");
protected static final Namespace CONFIG_NAMESPACE = Namespace.create("CONFIG");
protected static final Namespace GENERATORS_NAMESPACE = Namespace.create("GENERATORS");
private static final String RESOLUTION_PASS = "PASS";
private static final String RESOLUTION_FAIL = "FAIL";
private static final String BROWSER_CONFIG_KEY = "browser";
private boolean hasBeforeAllFail = false;
private String beforeAllThrowableMessage;
@Override
public void beforeAll(ExtensionContext extensionContext) {
extensionContext.getStore(ENDPOINT_NAMESPACE).put(ACTIVE_ENDPOINTS, new CopyOnWriteArraySet<Class<? extends Endpoint>>());
}
@Override
public void beforeEach(ExtensionContext extensionContext) {
extensionContext.getStore(ENDPOINT_NAMESPACE).put(ACTIVE_ENDPOINTS, new CopyOnWriteArraySet<Class<? extends Endpoint>>());
extensionContext.getStore(ExtensionContext.Namespace.create("UPLOADS")).remove("jira.uploads");
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return Stream.<Supplier<Optional<?>>>of(
() -> getTestContext(parameterContext),
() -> getAuthKey(parameterContext))
.map(Supplier::get)
.anyMatch(Optional::isPresent);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
storeCurrentTestName(extensionContext);
return Stream.<Supplier<Optional<?>>>of(
() -> getTestContext(parameterContext).map(tc -> {
try {
return tc.getType().getConstructor(Store.class, Store.class, Store.class, Store.class, Store.class)
.newInstance(
extensionContext.getRoot().getStore(Namespace.GLOBAL),
extensionContext.getStore(Namespace.GLOBAL),
extensionContext.getStore(ENDPOINT_NAMESPACE),
extensionContext.getStore(CONFIG_NAMESPACE),
extensionContext.getStore(GENERATORS_NAMESPACE));
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new ParameterResolutionException("Failed to initialize parameter " +
parameterContext.getParameter().getType().getSimpleName(), e);
}
}),
() -> getAuthKey(parameterContext)
.map(ak -> ak.getAnnotation(Key.class).value())
.map(ak -> AuthSupport.getCredentials(ak, new BaseStoreAccessor(
extensionContext.getRoot().getStore(Namespace.GLOBAL),
extensionContext.getStore(Namespace.GLOBAL),
extensionContext.getStore(ENDPOINT_NAMESPACE),
extensionContext.getStore(CONFIG_NAMESPACE),
extensionContext.getStore(GENERATORS_NAMESPACE)) {
})))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElseThrow(() -> new ParameterResolutionException("Parameter of type " +
parameterContext.getParameter().getType().getSimpleName() +
" is not supported"));
}
@Override
public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable {
processException(extensionContext);
throw Optional.of(throwable)
.filter(HarnessException.class::isInstance)
.map(AssertionError::new)
.map(Throwable.class::cast)
.orElse(throwable);
}
@Override
public void handleBeforeAllMethodExecutionException(ExtensionContext extensionContext, Throwable throwable) {
//This is workaround for possible bug in JUnit5 and Surefire - in case of beforeAll exception are test skipped instead of failed
//https://github.com/junit-team/junit5/issues/2178
hasBeforeAllFail = true;
beforeAllThrowableMessage = throwable.toString();
processException(extensionContext);
JiraTestResultPublisher.logJiraTestResult(extensionContext, throwable);
logger.error("Method @BeforeAll in test class {} failed with exception: {}.", () -> extensionContext.getRequiredTestClass().getName(), () -> beforeAllThrowableMessage);
}
@Override
public void beforeTestExecution(ExtensionContext extensionContext) {
if (hasBeforeAllFail) {
throw new BeforeAllHarnessException("Exception in before all occurred. Original exception message: " + beforeAllThrowableMessage);
}
}
private void processException(ExtensionContext extensionContext) {
Set<String> files = processWebEndpoints(extensionContext);
processMobileEndpoints(extensionContext);
processGreenscreenEndpoints(extensionContext);
Store uploads = extensionContext.getStore(Namespace.create("UPLOADS"));
uploads.put("jira.uploads", files);
}
private void processGreenscreenEndpoints(ExtensionContext extensionContext) {
getActiveEndpoints(extensionContext).stream()
.filter(p -> GreenScreenEndpoint.class.isAssignableFrom(p.getLeft()))
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, GreenScreenEndpoint.class))
.forEach(e -> logger.info("Failed on screen:\n" + e.getText(1, 1, 0)));
}
private void processMobileEndpoints(ExtensionContext extensionContext) {
getActiveEndpoints(extensionContext).stream()
.filter(p -> MobileEndpoint.class.isAssignableFrom(p.getLeft()))
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, MobileEndpoint.class))
.forEach(mobileEndpoint -> {
mobileEndpoint.takeSnapshot(extensionContext.getDisplayName());
mobileEndpoint.saveSources(extensionContext.getDisplayName());
});
}
private Set<String> processWebEndpoints(ExtensionContext extensionContext) {
return getActiveEndpoints(extensionContext).stream()
.filter(p -> WebEndpoint.class.isAssignableFrom(p.getLeft()))
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, WebEndpoint.class))
.map(webEndpoint -> {
Set<String> files = new HashSet<>();
String filePrefix = (extensionContext.getTestClass().get().getSimpleName() + "_" + extensionContext.getDisplayName()).replaceAll("[\\\\/:*?\"<>|]", "");
files.add(webEndpoint.takeSnapshot(filePrefix));
files.addAll(webEndpoint.captureLogs(filePrefix));
webEndpoint.captureDom(filePrefix);
return files;
})
.filter(set -> !set.isEmpty())
.collect(HashSet::new, Set::addAll, Set::addAll);
}
@SuppressWarnings("unchecked")
private Set<Pair<Class<? extends Endpoint>, Object[]>> getActiveEndpoints(ExtensionContext extensionContext) {
return extensionContext.getStore(ENDPOINT_NAMESPACE).get(ACTIVE_ENDPOINTS, Set.class);
}
private void closeActiveEndpoints(ExtensionContext extensionContext) {
getActiveEndpoints(extensionContext).stream()
.filter(Objects::nonNull)
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).remove(c, Endpoint.class))
.peek(e -> {
if (e instanceof MobileEndpoint) {
((MobileEndpoint) e).captureVideo(extensionContext.getDisplayName());
}
})
.forEach(Endpoint::close);
}
@Override
public void afterEach(ExtensionContext extensionContext) {
closeActiveEndpoints(extensionContext);
if (!hasBeforeAllFail) {
JiraTestResultPublisher.logJiraTestResult(extensionContext);
}
logTestResult(extensionContext);
}
@Override
public void afterAll(ExtensionContext extensionContext) {
//Closes only endpoints initialized in BeforeAll and not used in test methods
closeActiveEndpoints(extensionContext);
}
private Optional<Parameter> getAuthKey(ParameterContext parameterContext) {
return Optional.of(parameterContext.getParameter())
.filter(p -> p.getAnnotation(Key.class) != null)
.filter(p -> Credentials.class.isAssignableFrom(p.getType()));
}
private Optional<Parameter> getTestContext(ParameterContext parameterContext) {
return Optional.of(parameterContext.getParameter())
.filter(p -> p.getType().getAnnotation(TestContext.class) != null)
.filter(p -> StoreAccessor.class.isAssignableFrom(p.getType()));
}
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
if (isClassExtensionContext(context)) {
return evaluateClassExecutionContext(context);
}
Environment environment = getCurrentEnvironment(context);
return context.getTestMethod()
.filter(m -> environment != null)
.flatMap(m -> Stream.<Supplier<Optional<ConditionEvaluationResult>>>of(
() -> checkTestCaseEnvironments(environment, m, context.getDisplayName()),
() -> checkDefectEnvironments(environment, m, context.getDisplayName(), context),
() -> checkGlobalParameterSet(context, m, context.getDisplayName()),
() -> checkJiraReportingRules(context, m, context.getDisplayName()))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst())
.orElse(enableEvaluationAndLogExecutionStart(context));
}
private boolean isClassExtensionContext(ExtensionContext extensionContext) {
String simpleTestName = extensionContext.getTestClass()
.get()
.getSimpleName();
return !extensionContext.getTestMethod().isPresent() && extensionContext.getDisplayName().equals(simpleTestName);
}
private ConditionEvaluationResult evaluateClassExecutionContext(ExtensionContext extensionContext) {
ConfigProvider.readConfig().forEach((k, v) -> extensionContext.getStore(CONFIG_NAMESPACE).put(k, v));
Browser definedBrowser = getBrowserFromConfig(extensionContext);
String className = getClassCanonicalName(extensionContext);
Class<?> testClass = extensionContext.getTestClass().get();
return Stream.<Supplier<Optional<Browser[]>>>of(
() -> Optional.ofNullable(testClass.getAnnotation(TestScenario.class))
.map(TestScenario::browsers),
() -> Optional.ofNullable(testClass.getAnnotation(NonConcurrentTestScenario.class))
.map(NonConcurrentTestScenario::browsers),
() -> Optional.ofNullable(testClass.getAnnotation(TestScenarioWithOrder.class))
.map(TestScenarioWithOrder::browsers))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.filter(browsers -> Arrays.stream(browsers)
.noneMatch(definedBrowser::equals))
.map(browsers -> ConditionEvaluationResult.disabled("Tests in class " +
className + " disabled. It is marked to only run on " +
Arrays.stream(browsers)
.map(Enum::name)
.collect(Collectors.joining(", "))))
.orElse(enableClassForTests(className));
}
private ConditionEvaluationResult enableClassForTests(String className) {
logger.info("Test execution in test class {} started.", () -> className);
return ConditionEvaluationResult.enabled("Tests in " + className + " enabled.");
}
private Optional<ConditionEvaluationResult> checkGlobalParameterSet(ExtensionContext extensionContext, Method testMethod, String testName) {
Store rootStore = extensionContext.getRoot().getStore(Namespace.GLOBAL);
return Optional.of(Arrays.stream(testMethod.getAnnotationsByType(EnableIfSet.class))
.map(annotation -> {
if (rootStore == null) {
return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Need to check key '%s' " +
"to be set, but root context is null. Skipping...", testName, annotation.globalKey()));
}
Object value = rootStore.get(annotation.globalKey());
if (value == null) {
return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Key '%s' " +
"is not set in global context. Skipping...", testName, annotation.globalKey()));
}
return null;
}).filter(Objects::nonNull)
.findFirst()
.orElse(ConditionEvaluationResult.enabled("Test " + testName + " enabled")));
}
private Optional<ConditionEvaluationResult> checkJiraReportingRules(ExtensionContext extensionContext, Method testMethod, String testName) {
if (Boolean.parseBoolean(extensionContext.getStore(CONFIG_NAMESPACE).get("reports.tmfj.publish", String.class))) {
return Optional.of(Arrays.stream(testMethod.getAnnotationsByType(JiraTestCase.class))
.map(annotation -> {
if (JiraTestResultPublisher.doesTestCaseExist(annotation.id(), extensionContext)) {
return null;
} else {
return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Cannot find Test Case with id '%s' " +
"in Jira, but reporting to Jira is requested. Skipping...", testName, annotation.id()));
}
}).filter(Objects::nonNull)
.findFirst()
.orElse(ConditionEvaluationResult.enabled("Test " + testName + " enabled")));
}
return Optional.empty();
}
private Optional<ConditionEvaluationResult> checkTestCaseEnvironments(Environment environment, Method testMethod,
String testName) {
return Stream.<Supplier<Optional<Environment[]>>>of(
() -> Optional.ofNullable(testMethod.getAnnotation(TestCase.class))
.map(TestCase::environments),
() -> Optional.ofNullable(testMethod.getAnnotation(ParameterizedTestCase.class))
.map(ParameterizedTestCase::environments))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.filter(envs -> Arrays.stream(envs).noneMatch(environment::equals))
.map(envs -> ConditionEvaluationResult.disabled("Test " +
testName + " disabled. It is marked to only run on " +
Arrays.stream(envs).map(Enum::name).collect(Collectors.joining(", "))));
}
private Optional<ConditionEvaluationResult> checkDefectEnvironments(Environment environment, Method testMethod,
String testName, ExtensionContext context) {
return Optional.of(context.getStore(CONFIG_NAMESPACE)
.getOrComputeIfAbsent("harness.defects.ignore", k -> "false"))
.filter(ignore -> !"true".equals(ignore))
.flatMap(i -> Arrays.stream(testMethod.getAnnotationsByType(Defect.class))
.filter(d -> Arrays.stream(d.environments()).anyMatch(environment::equals))
.findFirst())
.map(d -> ConditionEvaluationResult.disabled("Test " +
testName + " in environment " + environment + " disabled due to defect '" + d.value() + "'"));
}
private Environment getCurrentEnvironment(ExtensionContext context) {
return Optional.ofNullable(context.getStore(CONFIG_NAMESPACE)
.get(HarnessConfigConstants.ENVIRONMENT_TYPE))
.filter(String.class::isInstance)
.map(String.class::cast)
.map(Environment::fromString)
.map(e -> {
logger.info("Detected environment: {}", () -> e.name());
return e;
})
.orElseThrow(() ->
new HarnessConfigurationException("You need to configure mandatory parameter " + HarnessConfigConstants.ENVIRONMENT_TYPE));
}
private Browser getBrowserFromConfig(ExtensionContext extensionContext) {
String browserConfigName = System.getProperty(BROWSER_CONFIG_KEY);
if (browserConfigName == null) {
browserConfigName = Optional.ofNullable(extensionContext.getStore(CONFIG_NAMESPACE).get(BROWSER_CONFIG_KEY, String.class))
.orElseThrow(() -> new HarnessConfigurationException(("You need to configure browser to run tests!")));
}
Browser browser = Browser.getBrowserByConfigName(browserConfigName);
logger.info("Detected browser: {}", browser::name);
return browser;
}
private ConditionEvaluationResult enableEvaluationAndLogExecutionStart(ExtensionContext extensionContext) {
String testName = extensionContext.getDisplayName();
logger.info("Test case {} execution in test class {} started.", () -> testName, () -> getClassCanonicalName(extensionContext));
return ConditionEvaluationResult.enabled("Test " + testName + " enabled");
}
private void logTestResult(ExtensionContext extensionContext) {
Method testMethod = extensionContext.getRequiredTestMethod();
Optional<Throwable> executionException = extensionContext.getExecutionException();
String resolution = executionException.map(e -> RESOLUTION_FAIL).orElse(RESOLUTION_PASS);
logger.info("Test case {} in test class {} executed with resolution - {}.", () -> extensionContext.getDisplayName(), () -> testMethod.getDeclaringClass(), () -> resolution);
if (resolution.equals(RESOLUTION_FAIL)) {
logger.error("ERROR: ", () -> executionException.get());
}
}
private String getClassCanonicalName(ExtensionContext extensionContext) {
return extensionContext.getTestClass()
.get()
.getCanonicalName();
}
private void storeCurrentTestName(ExtensionContext extensionContext) {
String uniqueId = UniqueId.parse(extensionContext.getUniqueId())
.getSegments()
.stream()
.skip(1)
.map(UniqueId.Segment::getValue)
.collect(Collectors.joining("."));
extensionContext.getRoot().getStore(Namespace.GLOBAL).put(HarnessConfigConstants.TEST_UNIQUE_ID, uniqueId);
String shortId = (extensionContext.getTestClass().get().getSimpleName() + "_" + extensionContext.getDisplayName()).replaceAll("[\\\\/:*?\"<>|]", "");
extensionContext.getRoot().getStore(Namespace.GLOBAL).put(HarnessConfigConstants.TEST_SHORT_ID, shortId);
}
package cz.moneta.test.harness;
import cz.moneta.test.harness.annotations.*;
import cz.moneta.test.harness.config.ConfigProvider;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.BaseStoreAccessor;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.endpoints.MobileEndpoint;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import cz.moneta.test.harness.endpoints.greenscreen.GreenScreenEndpoint;
import cz.moneta.test.harness.endpoints.jira.JiraTestResultPublisher;
import cz.moneta.test.harness.exception.BeforeAllHarnessException;
import cz.moneta.test.harness.exception.HarnessConfigurationException;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.auth.AuthSupport;
import cz.moneta.test.harness.support.auth.Credentials;
import cz.moneta.test.harness.support.auth.Key;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.*;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.platform.engine.UniqueId;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class HarnessJunit5Extension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver,
TestExecutionExceptionHandler, AfterEachCallback, ExecutionCondition, LifecycleMethodExecutionExceptionHandler,
BeforeTestExecutionCallback, AfterAllCallback {
private static final Logger logger = LogManager.getLogger(HarnessJunit5Extension.class);
public static final String ACTIVE_ENDPOINTS = "ACTIVE_ENDPOINTS";
private static final Namespace ENDPOINT_NAMESPACE = Namespace.create("ENDPOINT");
protected static final Namespace CONFIG_NAMESPACE = Namespace.create("CONFIG");
protected static final Namespace GENERATORS_NAMESPACE = Namespace.create("GENERATORS");
private static final String RESOLUTION_PASS = "PASS";
private static final String RESOLUTION_FAIL = "FAIL";
private static final String BROWSER_CONFIG_KEY = "browser";
private boolean hasBeforeAllFail = false;
private String beforeAllThrowableMessage;
@Override
public void beforeAll(ExtensionContext extensionContext) {
extensionContext.getStore(ENDPOINT_NAMESPACE).put(ACTIVE_ENDPOINTS, new CopyOnWriteArraySet<Class<? extends Endpoint>>());
}
@Override
public void beforeEach(ExtensionContext extensionContext) {
extensionContext.getStore(ENDPOINT_NAMESPACE).put(ACTIVE_ENDPOINTS, new CopyOnWriteArraySet<Class<? extends Endpoint>>());
extensionContext.getStore(ExtensionContext.Namespace.create("UPLOADS")).remove("jira.uploads");
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return Stream.<Supplier<Optional<?>>>of(
() -> getTestContext(parameterContext),
() -> getAuthKey(parameterContext))
.map(Supplier::get)
.anyMatch(Optional::isPresent);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
storeCurrentTestName(extensionContext);
return Stream.<Supplier<Optional<?>>>of(
() -> getTestContext(parameterContext).map(tc -> {
try {
return tc.getType().getConstructor(Store.class, Store.class, Store.class, Store.class, Store.class)
.newInstance(
extensionContext.getRoot().getStore(Namespace.GLOBAL),
extensionContext.getStore(Namespace.GLOBAL),
extensionContext.getStore(ENDPOINT_NAMESPACE),
extensionContext.getStore(CONFIG_NAMESPACE),
extensionContext.getStore(GENERATORS_NAMESPACE));
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new ParameterResolutionException("Failed to initialize parameter " +
parameterContext.getParameter().getType().getSimpleName(), e);
}
}),
() -> getAuthKey(parameterContext)
.map(ak -> ak.getAnnotation(Key.class).value())
.map(ak -> AuthSupport.getCredentials(ak, new BaseStoreAccessor(
extensionContext.getRoot().getStore(Namespace.GLOBAL),
extensionContext.getStore(Namespace.GLOBAL),
extensionContext.getStore(ENDPOINT_NAMESPACE),
extensionContext.getStore(CONFIG_NAMESPACE),
extensionContext.getStore(GENERATORS_NAMESPACE)) {
})))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElseThrow(() -> new ParameterResolutionException("Parameter of type " +
parameterContext.getParameter().getType().getSimpleName() +
" is not supported"));
}
@Override
public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable {
processException(extensionContext);
throw Optional.of(throwable)
.filter(HarnessException.class::isInstance)
.map(AssertionError::new)
.map(Throwable.class::cast)
.orElse(throwable);
}
@Override
public void handleBeforeAllMethodExecutionException(ExtensionContext extensionContext, Throwable throwable) {
//This is workaround for possible bug in JUnit5 and Surefire - in case of beforeAll exception are test skipped instead of failed
//https://github.com/junit-team/junit5/issues/2178
hasBeforeAllFail = true;
beforeAllThrowableMessage = throwable.toString();
processException(extensionContext);
JiraTestResultPublisher.logJiraTestResult(extensionContext, throwable);
logger.error("Method @BeforeAll in test class {} failed with exception: {}.", () -> extensionContext.getRequiredTestClass().getName(), () -> beforeAllThrowableMessage);
}
@Override
public void beforeTestExecution(ExtensionContext extensionContext) {
if (hasBeforeAllFail) {
throw new BeforeAllHarnessException("Exception in before all occurred. Original exception message: " + beforeAllThrowableMessage);
}
}
private void processException(ExtensionContext extensionContext) {
Set<String> files = processWebEndpoints(extensionContext);
processMobileEndpoints(extensionContext);
processGreenscreenEndpoints(extensionContext);
Store uploads = extensionContext.getStore(Namespace.create("UPLOADS"));
uploads.put("jira.uploads", files);
}
private void processGreenscreenEndpoints(ExtensionContext extensionContext) {
getActiveEndpoints(extensionContext).stream()
.filter(p -> GreenScreenEndpoint.class.isAssignableFrom(p.getLeft()))
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, GreenScreenEndpoint.class))
.forEach(e -> logger.info("Failed on screen:\n" + e.getText(1, 1, 0)));
}
private void processMobileEndpoints(ExtensionContext extensionContext) {
getActiveEndpoints(extensionContext).stream()
.filter(p -> MobileEndpoint.class.isAssignableFrom(p.getLeft()))
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, MobileEndpoint.class))
.forEach(mobileEndpoint -> {
mobileEndpoint.takeSnapshot(extensionContext.getDisplayName());
mobileEndpoint.saveSources(extensionContext.getDisplayName());
});
}
private Set<String> processWebEndpoints(ExtensionContext extensionContext) {
return getActiveEndpoints(extensionContext).stream()
.filter(p -> WebEndpoint.class.isAssignableFrom(p.getLeft()))
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, WebEndpoint.class))
.map(webEndpoint -> {
Set<String> files = new HashSet<>();
String filePrefix = (extensionContext.getTestClass().get().getSimpleName() + "_" + extensionContext.getDisplayName()).replaceAll("[\\\\/:*?\"<>|]", "");
files.add(webEndpoint.takeSnapshot(filePrefix));
files.addAll(webEndpoint.captureLogs(filePrefix));
webEndpoint.captureDom(filePrefix);
return files;
})
.filter(set -> !set.isEmpty())
.collect(HashSet::new, Set::addAll, Set::addAll);
}
@SuppressWarnings("unchecked")
private Set<Pair<Class<? extends Endpoint>, Object[]>> getActiveEndpoints(ExtensionContext extensionContext) {
return extensionContext.getStore(ENDPOINT_NAMESPACE).get(ACTIVE_ENDPOINTS, Set.class);
}
private void closeActiveEndpoints(ExtensionContext extensionContext) {
getActiveEndpoints(extensionContext).stream()
.filter(Objects::nonNull)
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).remove(c, Endpoint.class))
.peek(e -> {
if (e instanceof MobileEndpoint) {
((MobileEndpoint) e).captureVideo(extensionContext.getDisplayName());
}
})
.forEach(Endpoint::close);
}
@Override
public void afterEach(ExtensionContext extensionContext) {
closeActiveEndpoints(extensionContext);
if (!hasBeforeAllFail) {
JiraTestResultPublisher.logJiraTestResult(extensionContext);
}
logTestResult(extensionContext);
}
@Override
public void afterAll(ExtensionContext extensionContext) {
//Closes only endpoints initialized in BeforeAll and not used in test methods
closeActiveEndpoints(extensionContext);
}
private Optional<Parameter> getAuthKey(ParameterContext parameterContext) {
return Optional.of(parameterContext.getParameter())
.filter(p -> p.getAnnotation(Key.class) != null)
.filter(p -> Credentials.class.isAssignableFrom(p.getType()));
}
private Optional<Parameter> getTestContext(ParameterContext parameterContext) {
return Optional.of(parameterContext.getParameter())
.filter(p -> p.getType().getAnnotation(TestContext.class) != null)
.filter(p -> StoreAccessor.class.isAssignableFrom(p.getType()));
}
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
if (isClassExtensionContext(context)) {
return evaluateClassExecutionContext(context);
}
Environment environment = getCurrentEnvironment(context);
return context.getTestMethod()
.filter(m -> environment != null)
.flatMap(m -> Stream.<Supplier<Optional<ConditionEvaluationResult>>>of(
() -> checkTestCaseEnvironments(environment, m, context.getDisplayName()),
() -> checkDefectEnvironments(environment, m, context.getDisplayName(), context),
() -> checkGlobalParameterSet(context, m, context.getDisplayName()),
() -> checkJiraReportingRules(context, m, context.getDisplayName()))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst())
.orElse(enableEvaluationAndLogExecutionStart(context));
}
private boolean isClassExtensionContext(ExtensionContext extensionContext) {
String simpleTestName = extensionContext.getTestClass()
.get()
.getSimpleName();
return !extensionContext.getTestMethod().isPresent() && extensionContext.getDisplayName().equals(simpleTestName);
}
private ConditionEvaluationResult evaluateClassExecutionContext(ExtensionContext extensionContext) {
ConfigProvider.readConfig().forEach((k, v) -> extensionContext.getStore(CONFIG_NAMESPACE).put(k, v));
Browser definedBrowser = getBrowserFromConfig(extensionContext);
String className = getClassCanonicalName(extensionContext);
Class<?> testClass = extensionContext.getTestClass().get();
return Stream.<Supplier<Optional<Browser[]>>>of(
() -> Optional.ofNullable(testClass.getAnnotation(TestScenario.class))
.map(TestScenario::browsers),
() -> Optional.ofNullable(testClass.getAnnotation(NonConcurrentTestScenario.class))
.map(NonConcurrentTestScenario::browsers),
() -> Optional.ofNullable(testClass.getAnnotation(TestScenarioWithOrder.class))
.map(TestScenarioWithOrder::browsers))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.filter(browsers -> Arrays.stream(browsers)
.noneMatch(definedBrowser::equals))
.map(browsers -> ConditionEvaluationResult.disabled("Tests in class " +
className + " disabled. It is marked to only run on " +
Arrays.stream(browsers)
.map(Enum::name)
.collect(Collectors.joining(", "))))
.orElse(enableClassForTests(className));
}
private ConditionEvaluationResult enableClassForTests(String className) {
logger.info("Test execution in test class {} started.", () -> className);
return ConditionEvaluationResult.enabled("Tests in " + className + " enabled.");
}
private Optional<ConditionEvaluationResult> checkGlobalParameterSet(ExtensionContext extensionContext, Method testMethod, String testName) {
Store rootStore = extensionContext.getRoot().getStore(Namespace.GLOBAL);
return Optional.of(Arrays.stream(testMethod.getAnnotationsByType(EnableIfSet.class))
.map(annotation -> {
if (rootStore == null) {
return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Need to check key '%s' " +
"to be set, but root context is null. Skipping...", testName, annotation.globalKey()));
}
Object value = rootStore.get(annotation.globalKey());
if (value == null) {
return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Key '%s' " +
"is not set in global context. Skipping...", testName, annotation.globalKey()));
}
return null;
}).filter(Objects::nonNull)
.findFirst()
.orElse(ConditionEvaluationResult.enabled("Test " + testName + " enabled")));
}
private Optional<ConditionEvaluationResult> checkJiraReportingRules(ExtensionContext extensionContext, Method testMethod, String testName) {
if (Boolean.parseBoolean(extensionContext.getStore(CONFIG_NAMESPACE).get("reports.tmfj.publish", String.class))) {
return Optional.of(Arrays.stream(testMethod.getAnnotationsByType(JiraTestCase.class))
.map(annotation -> {
if (JiraTestResultPublisher.doesTestCaseExist(annotation.id(), extensionContext)) {
return null;
} else {
return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Cannot find Test Case with id '%s' " +
"in Jira, but reporting to Jira is requested. Skipping...", testName, annotation.id()));
}
}).filter(Objects::nonNull)
.findFirst()
.orElse(ConditionEvaluationResult.enabled("Test " + testName + " enabled")));
}
return Optional.empty();
}
private Optional<ConditionEvaluationResult> checkTestCaseEnvironments(Environment environment, Method testMethod,
String testName) {
return Stream.<Supplier<Optional<Environment[]>>>of(
() -> Optional.ofNullable(testMethod.getAnnotation(TestCase.class))
.map(TestCase::environments),
() -> Optional.ofNullable(testMethod.getAnnotation(ParameterizedTestCase.class))
.map(ParameterizedTestCase::environments))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.filter(envs -> Arrays.stream(envs).noneMatch(environment::equals))
.map(envs -> ConditionEvaluationResult.disabled("Test " +
testName + " disabled. It is marked to only run on " +
Arrays.stream(envs).map(Enum::name).collect(Collectors.joining(", "))));
}
private Optional<ConditionEvaluationResult> checkDefectEnvironments(Environment environment, Method testMethod,
String testName, ExtensionContext context) {
return Optional.of(context.getStore(CONFIG_NAMESPACE)
.getOrComputeIfAbsent("harness.defects.ignore", k -> "false"))
.filter(ignore -> !"true".equals(ignore))
.flatMap(i -> Arrays.stream(testMethod.getAnnotationsByType(Defect.class))
.filter(d -> Arrays.stream(d.environments()).anyMatch(environment::equals))
.findFirst())
.map(d -> ConditionEvaluationResult.disabled("Test " +
testName + " in environment " + environment + " disabled due to defect '" + d.value() + "'"));
}
private Environment getCurrentEnvironment(ExtensionContext context) {
return Optional.ofNullable(context.getStore(CONFIG_NAMESPACE)
.get(HarnessConfigConstants.ENVIRONMENT_TYPE))
.filter(String.class::isInstance)
.map(String.class::cast)
.map(Environment::fromString)
.map(e -> {
logger.info("Detected environment: {}", () -> e.name());
return e;
})
.orElseThrow(() ->
new HarnessConfigurationException("You need to configure mandatory parameter " + HarnessConfigConstants.ENVIRONMENT_TYPE));
}
private Browser getBrowserFromConfig(ExtensionContext extensionContext) {
String browserConfigName = System.getProperty(BROWSER_CONFIG_KEY);
if (browserConfigName == null) {
browserConfigName = Optional.ofNullable(extensionContext.getStore(CONFIG_NAMESPACE).get(BROWSER_CONFIG_KEY, String.class))
.orElseThrow(() -> new HarnessConfigurationException(("You need to configure browser to run tests!")));
}
Browser browser = Browser.getBrowserByConfigName(browserConfigName);
logger.info("Detected browser: {}", browser::name);
return browser;
}
private ConditionEvaluationResult enableEvaluationAndLogExecutionStart(ExtensionContext extensionContext) {
String testName = extensionContext.getDisplayName();
logger.info("Test case {} execution in test class {} started.", () -> testName, () -> getClassCanonicalName(extensionContext));
return ConditionEvaluationResult.enabled("Test " + testName + " enabled");
}
private void logTestResult(ExtensionContext extensionContext) {
Method testMethod = extensionContext.getRequiredTestMethod();
Optional<Throwable> executionException = extensionContext.getExecutionException();
String resolution = executionException.map(e -> RESOLUTION_FAIL).orElse(RESOLUTION_PASS);
logger.info("Test case {} in test class {} executed with resolution - {}.", () -> extensionContext.getDisplayName(), () -> testMethod.getDeclaringClass(), () -> resolution);
if (resolution.equals(RESOLUTION_FAIL)) {
logger.error("ERROR: ", () -> executionException.get());
}
}
private String getClassCanonicalName(ExtensionContext extensionContext) {
return extensionContext.getTestClass()
.get()
.getCanonicalName();
}
private void storeCurrentTestName(ExtensionContext extensionContext) {
String uniqueId = UniqueId.parse(extensionContext.getUniqueId())
.getSegments()
.stream()
.skip(1)
.map(UniqueId.Segment::getValue)
.collect(Collectors.joining("."));
extensionContext.getRoot().getStore(Namespace.GLOBAL).put(HarnessConfigConstants.TEST_UNIQUE_ID, uniqueId);
String shortId = (extensionContext.getTestClass().get().getSimpleName() + "_" + extensionContext.getDisplayName()).replaceAll("[\\\\/:*?\"<>|]", "");
extensionContext.getRoot().getStore(Namespace.GLOBAL).put(HarnessConfigConstants.TEST_SHORT_ID, shortId);
}
}

View File

@ -1,33 +1,33 @@
package cz.moneta.test.harness.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static cz.moneta.test.harness.annotations.Environment.DEV;
import static cz.moneta.test.harness.annotations.Environment.EDU;
import static cz.moneta.test.harness.annotations.Environment.FVE;
import static cz.moneta.test.harness.annotations.Environment.PPE;
import static cz.moneta.test.harness.annotations.Environment.TST1;
/**
* Disables test in specified environments with a reference to the related issue
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Defects.class)
public @interface Defect {
/**
* Reason for test being disabled (ideally) including a reference to a JIRA or TEAMTRACK issue
*/
String value();
/**
* Applicable environments where the test should be effectively disabled (default is all environments)
*/
Environment[] environments() default {DEV, TST1, PPE, EDU, FVE};
}
package cz.moneta.test.harness.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static cz.moneta.test.harness.annotations.Environment.DEV;
import static cz.moneta.test.harness.annotations.Environment.EDU;
import static cz.moneta.test.harness.annotations.Environment.FVE;
import static cz.moneta.test.harness.annotations.Environment.PPE;
import static cz.moneta.test.harness.annotations.Environment.TST1;
/**
* Disables test in specified environments with a reference to the related issue
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Defects.class)
public @interface Defect {
/**
* Reason for test being disabled (ideally) including a reference to a JIRA or TEAMTRACK issue
*/
String value();
/**
* Applicable environments where the test should be effectively disabled (default is all environments)
*/
Environment[] environments() default {DEV, TST1, PPE, EDU, FVE};
}

View File

@ -1,17 +1,17 @@
package cz.moneta.test.harness.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* container annotation for {@link cz.moneta.test.harness.annotations.Defect}
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Defects {
Defect[] value();
}
package cz.moneta.test.harness.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* container annotation for {@link cz.moneta.test.harness.annotations.Defect}
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Defects {
Defect[] value();
}

View File

@ -1,13 +1,13 @@
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(EnableIfSets.class)
@Documented
public @interface EnableIfSet {
String globalKey();
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(EnableIfSets.class)
@Documented
public @interface EnableIfSet {
String globalKey();
}

View File

@ -1,15 +1,15 @@
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
/**
* Container annotation
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableIfSets {
EnableIfSet[] value();
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
/**
* Container annotation
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableIfSets {
EnableIfSet[] value();
}

View File

@ -1,30 +1,30 @@
package cz.moneta.test.harness.annotations;
import java.util.EnumSet;
public enum Environment {
DEV("DigDev"),
TST1("TST1"),
TST3("TST3"),
PPE("PPE"),
EDU("EDU"),
FVE("FVE"),
LIVE("LIVE");
Environment(String jiraAlias) {
this.jiraAlias = jiraAlias;
}
private String jiraAlias;
public static Environment fromString(String env) {
return EnumSet.allOf(Environment.class).stream()
.filter(e -> e.name().equalsIgnoreCase(env))
.findFirst()
.orElse(null);
}
public String getJiraAlias() {
return jiraAlias;
}
}
package cz.moneta.test.harness.annotations;
import java.util.EnumSet;
public enum Environment {
DEV("DigDev"),
TST1("TST1"),
TST3("TST3"),
PPE("PPE"),
EDU("EDU"),
FVE("FVE"),
LIVE("LIVE");
Environment(String jiraAlias) {
this.jiraAlias = jiraAlias;
}
private String jiraAlias;
public static Environment fromString(String env) {
return EnumSet.allOf(Environment.class).stream()
.filter(e -> e.name().equalsIgnoreCase(env))
.findFirst()
.orElse(null);
}
public String getJiraAlias() {
return jiraAlias;
}
}

View File

@ -1,22 +1,22 @@
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
/**
* {@code @JiraTestCase} is used to signal that the annotated method implements
* particular test case in Test Management for JIRA.
*
* The info in this annotation is used by harness to create Test Run objects in
* Test Management for JIRA when configured and enabled, otherwise it has no effect.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(JiraTestCases.class)
@Documented
public @interface JiraTestCase {
String id();
String project() default "";
}
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
/**
* {@code @JiraTestCase} is used to signal that the annotated method implements
* particular test case in Test Management for JIRA.
*
* The info in this annotation is used by harness to create Test Run objects in
* Test Management for JIRA when configured and enabled, otherwise it has no effect.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(JiraTestCases.class)
@Documented
public @interface JiraTestCase {
String id();
String project() default "";
}

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
/**
* Container annotation
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JiraTestCases {
JiraTestCase[] value();
}
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
/**
* Container annotation
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JiraTestCases {
JiraTestCase[] value();
}

View File

@ -1,31 +1,31 @@
package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension;
import cz.moneta.test.harness.data.Browser;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation is used for managing parallel test execution.
* Test class with this annotation will not be executed in parallel to other classes with this annotation.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RunWith(JUnitPlatform.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(HarnessJunit5Extension.class)
public @interface NonConcurrentTestScenario {
String name() default "";
Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME};
}
package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension;
import cz.moneta.test.harness.data.Browser;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation is used for managing parallel test execution.
* Test class with this annotation will not be executed in parallel to other classes with this annotation.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RunWith(JUnitPlatform.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(HarnessJunit5Extension.class)
public @interface NonConcurrentTestScenario {
String name() default "";
Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME};
}

View File

@ -1,17 +1,17 @@
package cz.moneta.test.harness.annotations;
import org.junit.jupiter.params.ParameterizedTest;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ParameterizedTest
public @interface ParameterizedTestCase {
String name () default "";
Environment[] environments() default {Environment.DEV, Environment.TST1, Environment.PPE, Environment.EDU, Environment.FVE};
}
package cz.moneta.test.harness.annotations;
import org.junit.jupiter.params.ParameterizedTest;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ParameterizedTest
public @interface ParameterizedTestCase {
String name () default "";
Environment[] environments() default {Environment.DEV, Environment.TST1, Environment.PPE, Environment.EDU, Environment.FVE};
}

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.annotations;
import org.junit.jupiter.api.Test;
import java.lang.annotation.*;
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Test
public @interface TestCase {
String name() default "";
Environment[] environments() default {Environment.DEV, Environment.TST1, Environment.PPE, Environment.EDU, Environment.FVE};
}
package cz.moneta.test.harness.annotations;
import org.junit.jupiter.api.Test;
import java.lang.annotation.*;
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Test
public @interface TestCase {
String name() default "";
Environment[] environments() default {Environment.DEV, Environment.TST1, Environment.PPE, Environment.EDU, Environment.FVE};
}

View File

@ -1,9 +1,9 @@
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestContext {
}
package cz.moneta.test.harness.annotations;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestContext {
}

View File

@ -1,26 +1,26 @@
package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension;
import cz.moneta.test.harness.data.Browser;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RunWith(JUnitPlatform.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(ExecutionMode.CONCURRENT)
@ExtendWith(HarnessJunit5Extension.class)
public @interface TestScenario {
String name() default "";
Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME};
package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension;
import cz.moneta.test.harness.data.Browser;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RunWith(JUnitPlatform.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(ExecutionMode.CONCURRENT)
@ExtendWith(HarnessJunit5Extension.class)
public @interface TestScenario {
String name() default "";
Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME};
}

View File

@ -1,29 +1,29 @@
package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension;
import cz.moneta.test.harness.data.Browser;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RunWith(JUnitPlatform.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(ExecutionMode.SAME_THREAD)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ExtendWith(HarnessJunit5Extension.class)
public @interface TestScenarioWithOrder {
String name() default "";
Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME};
package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension;
import cz.moneta.test.harness.data.Browser;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RunWith(JUnitPlatform.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(ExecutionMode.SAME_THREAD)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ExtendWith(HarnessJunit5Extension.class)
public @interface TestScenarioWithOrder {
String name() default "";
Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME};
}

View File

@ -1,19 +1,19 @@
package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RunWith(JUnitPlatform.class)
@ExtendWith(HarnessJunit5Extension.class)
public @interface TestSuite {
String name() default "";
}
package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RunWith(JUnitPlatform.class)
@ExtendWith(HarnessJunit5Extension.class)
public @interface TestSuite {
String name() default "";
}

View File

@ -1,117 +1,117 @@
package cz.moneta.test.harness.config;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.exception.HarnessConfigurationException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class ConfigProvider {
private static final Logger logger = LogManager.getLogger(ConfigProvider.class);
private ConfigProvider() {}
public static Map<String, String> readConfig() {
String fileName = Stream.<Supplier<String>>of(
() -> System.getProperty("config"),
() -> System.getenv("HARNESS_CONFIG"))
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> new HarnessConfigurationException("You need to provide a configuration file"));
Map<String, String> resultConfig = loadConfig(fileName, new ArrayList<>());
logger.debug("Effective configuration:");
resultConfig.entrySet()
.stream()
.filter(configEntry -> !configEntry.getKey().equals(HarnessConfigConstants.VAULT_PASSWORD_CONFIG)) //DO NOT LOG VAULT PWD VALUE!
.collect(Collectors.toMap(Map.Entry::getKey, configEntry -> configEntry.getValue().trim()))
.forEach((key, value) -> logger.debug("{}={}", key, value));
return resultConfig;
}
private static Map<String, String> loadConfig(String fileName, List<String> configStack) {
Map<String, String> properties = readProperties(fileName);
return Optional.ofNullable(properties.get("environment.from"))
.map(fn -> {
if (configStack.contains(fn)) {
throw new IllegalStateException("Config chain contains circular dependencies");
}
return fn;
})
.map(fn -> {
configStack.add(fn);
return loadConfig(fn, configStack);
})
.map(p -> {
p.putAll(properties);
return p;
})
.orElse(properties);
}
private static Map<String, String> readProperties(String fileName) {
Reader reader;
try {
logger.debug(() -> String.format("Trying to load config file: %s", fileName));
reader = new FileReader(fileName);
} catch (FileNotFoundException e) {
logger.debug(() ->
String.format("Could not load file: %s, trying to load it as classpath resource", fileName));
reader = getReaderForClasspathResource(fileName);
}
Properties properties = new Properties();
try {
properties.load(reader);
} catch (IOException e) {
throw new IllegalStateException("Cannot read config file", e);
}
return properties.stringPropertyNames().stream()
.collect(Collectors.toMap(k -> k, k -> resolveSystemProperty(properties.getProperty(k))));
}
private static String resolveSystemProperty(String value) {
return Stream.<Supplier<Optional<String>>>of(
() -> Optional.ofNullable(value).filter(v -> !value.startsWith("$")),
() -> Optional.ofNullable(value)
.map(v -> System.getProperty(v.substring(1)))
.map(v -> {
logger.debug("Successfully resolved system property: {}", () -> value);
return v;
}))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Cannot find system property for: " + value));
}
private static Reader getReaderForClasspathResource(String resourceName) {
String resourcePath = "envs/" + resourceName; //TODO hidden magic constant - fix this concept
InputStream resourceAsStream = ConfigProvider.class.getClassLoader().getResourceAsStream(resourcePath);
if (resourceAsStream != null) {
return new InputStreamReader(resourceAsStream);
} else {
throw new IllegalStateException(String.format("Cannot find config file: %s", resourceName));
}
}
}
package cz.moneta.test.harness.config;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.exception.HarnessConfigurationException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class ConfigProvider {
private static final Logger logger = LogManager.getLogger(ConfigProvider.class);
private ConfigProvider() {}
public static Map<String, String> readConfig() {
String fileName = Stream.<Supplier<String>>of(
() -> System.getProperty("config"),
() -> System.getenv("HARNESS_CONFIG"))
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> new HarnessConfigurationException("You need to provide a configuration file"));
Map<String, String> resultConfig = loadConfig(fileName, new ArrayList<>());
logger.debug("Effective configuration:");
resultConfig.entrySet()
.stream()
.filter(configEntry -> !configEntry.getKey().equals(HarnessConfigConstants.VAULT_PASSWORD_CONFIG)) //DO NOT LOG VAULT PWD VALUE!
.collect(Collectors.toMap(Map.Entry::getKey, configEntry -> configEntry.getValue().trim()))
.forEach((key, value) -> logger.debug("{}={}", key, value));
return resultConfig;
}
private static Map<String, String> loadConfig(String fileName, List<String> configStack) {
Map<String, String> properties = readProperties(fileName);
return Optional.ofNullable(properties.get("environment.from"))
.map(fn -> {
if (configStack.contains(fn)) {
throw new IllegalStateException("Config chain contains circular dependencies");
}
return fn;
})
.map(fn -> {
configStack.add(fn);
return loadConfig(fn, configStack);
})
.map(p -> {
p.putAll(properties);
return p;
})
.orElse(properties);
}
private static Map<String, String> readProperties(String fileName) {
Reader reader;
try {
logger.debug(() -> String.format("Trying to load config file: %s", fileName));
reader = new FileReader(fileName);
} catch (FileNotFoundException e) {
logger.debug(() ->
String.format("Could not load file: %s, trying to load it as classpath resource", fileName));
reader = getReaderForClasspathResource(fileName);
}
Properties properties = new Properties();
try {
properties.load(reader);
} catch (IOException e) {
throw new IllegalStateException("Cannot read config file", e);
}
return properties.stringPropertyNames().stream()
.collect(Collectors.toMap(k -> k, k -> resolveSystemProperty(properties.getProperty(k))));
}
private static String resolveSystemProperty(String value) {
return Stream.<Supplier<Optional<String>>>of(
() -> Optional.ofNullable(value).filter(v -> !value.startsWith("$")),
() -> Optional.ofNullable(value)
.map(v -> System.getProperty(v.substring(1)))
.map(v -> {
logger.debug("Successfully resolved system property: {}", () -> value);
return v;
}))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Cannot find system property for: " + value));
}
private static Reader getReaderForClasspathResource(String resourceName) {
String resourcePath = "envs/" + resourceName; //TODO hidden magic constant - fix this concept
InputStream resourceAsStream = ConfigProvider.class.getClassLoader().getResourceAsStream(resourcePath);
if (resourceAsStream != null) {
return new InputStreamReader(resourceAsStream);
} else {
throw new IllegalStateException(String.format("Cannot find config file: %s", resourceName));
}
}
}

View File

@ -1,7 +1,7 @@
package cz.moneta.test.harness.connectors;
public interface Connector {
default void close() {
}
}
package cz.moneta.test.harness.connectors;
public interface Connector {
default void close() {
}
}

View File

@ -1,39 +1,39 @@
package cz.moneta.test.harness.connectors;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class DemoConnector implements Connector {
private Path directory;
public void connectToTempDirectoryService(String name) {
try {
directory = Files.createTempDirectory(name);
} catch (IOException e) {
throw new RuntimeException("Cannot connect to temp filesystem service", e);
}
}
public void createFile(String name) {
try {
Files.createFile(directory.resolve(name));
} catch (IOException e) {
throw new RuntimeException(String.format("Cannot create file: %s", name), e);
}
}
public boolean fileExists(String name) {
return Files.exists(directory.resolve(name));
}
public void deleteFile(String name) {
try {
Files.delete(directory.resolve(name));
} catch (IOException e) {
throw new RuntimeException(String.format("Cannot delete file: %s", name), e);
}
}
}
package cz.moneta.test.harness.connectors;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class DemoConnector implements Connector {
private Path directory;
public void connectToTempDirectoryService(String name) {
try {
directory = Files.createTempDirectory(name);
} catch (IOException e) {
throw new RuntimeException("Cannot connect to temp filesystem service", e);
}
}
public void createFile(String name) {
try {
Files.createFile(directory.resolve(name));
} catch (IOException e) {
throw new RuntimeException(String.format("Cannot create file: %s", name), e);
}
}
public boolean fileExists(String name) {
return Files.exists(directory.resolve(name));
}
public void deleteFile(String name) {
try {
Files.delete(directory.resolve(name));
} catch (IOException e) {
throw new RuntimeException(String.format("Cannot delete file: %s", name), e);
}
}
}

View File

@ -1,156 +1,156 @@
package cz.moneta.test.harness.connectors;
import com.bytezone.dm3270.ConnectionListener;
import com.bytezone.dm3270.TerminalClient;
import com.bytezone.dm3270.commands.AIDCommand;
import com.bytezone.dm3270.display.ScreenDimensions;
import cz.moneta.test.harness.support.greenscreen.Key;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.glassfish.jersey.SslConfigurator;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class GreenScreenConnector implements Connector {
public static final String CLIENT = "08548";
private static final Logger LOG = LogManager.getLogger("GreenScreenConnector");
private final Semaphore lock = new Semaphore(0);
private final TerminalClient terminal;
private final String url;
private final String user;
private final String password;
public GreenScreenConnector(String url, String user, String password) {
this.url = url;
this.user = user;
this.password = password;
this.terminal = new TerminalClient(0, new ScreenDimensions(24, 80));
terminal.setConnectionTimeoutMillis(20_000);
terminal.setConnectionListener(buildConnectionListener());
terminal.addScreenChangeListener(sw -> {
LOG.debug(() -> "screen changed: \n" + toCp870(terminal.getScreenText()));
lock.release();
});
terminal.setSocketFactory(buildSocketFactory());
}
private static ConnectionListener buildConnectionListener() {
return new ConnectionListener() {
@Override
public void onConnection() {
LOG.info("CONNECTED!!!");
}
@Override
public void onException(Exception e) {
LOG.error("Green screen connection failed", e);
}
@Override
public void onConnectionClosed() {
LOG.info("CONNECTION CLOSED!!!");
}
};
}
private static String toCp870(String cp1047) {
try {
return new String(cp1047.getBytes("CP1047"), "CP870");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Error converting CP1047 to CP870");
}
}
private static String toCp1047(String cp870) {
try {
return new String(cp870.getBytes("CP870"), "CP1047");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Error converting CP870 to CP1047");
}
}
public void connectAndLogin() {
terminal.connect(url, 992);
waitForScreenChange();
terminal.setFieldTextByCoord(24, 1, "atrf");
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
terminal.setFieldTextByCoord(6, 25, CLIENT);
terminal.setFieldTextByCoord(8, 24, user);
terminal.setFieldTextByCoord(10, 24, password);
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
waitForScreenChange();
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
}
private void waitForScreenChange() {
boolean success = false;
try {
success = lock.tryAcquire(5L, TimeUnit.SECONDS);
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
LOG.error(e);
}
if (!success) {
throw new IllegalStateException("Failed to load screen within 5 second timeout");
}
}
private SSLSocketFactory buildSocketFactory() {
try (InputStream truststoreIs = this.getClass().getClassLoader()
.getResourceAsStream("keystores/greenScreenTrustStore")) {
KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustedStore.load(truststoreIs, "changeit".toCharArray());
return SslConfigurator.newInstance()
.trustStore(trustedStore)
.keyStorePassword("changeit".toCharArray())
.createSSLContext()
.getSocketFactory();
} catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) {
throw new IllegalStateException("Failed to initialize SSL context", e);
}
}
public String getText(int row, int column, int length) {
String screenText = toCp870(terminal.getScreenText());
if (length == 0) {
return screenText;
} else {
int beginIndex = (row - 1) * 81 + column - 1;
return screenText.substring(beginIndex, beginIndex + length);
}
}
public void pressKey(Key key) {
switch (key) {
case ENTER:
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
break;
default:
throw new IllegalArgumentException("Unknown key " + key.name());
}
}
public void type(int row, int column, String text) {
terminal.setFieldTextByCoord(row, column, toCp1047(text));
}
}
package cz.moneta.test.harness.connectors;
import com.bytezone.dm3270.ConnectionListener;
import com.bytezone.dm3270.TerminalClient;
import com.bytezone.dm3270.commands.AIDCommand;
import com.bytezone.dm3270.display.ScreenDimensions;
import cz.moneta.test.harness.support.greenscreen.Key;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.glassfish.jersey.SslConfigurator;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class GreenScreenConnector implements Connector {
public static final String CLIENT = "08548";
private static final Logger LOG = LogManager.getLogger("GreenScreenConnector");
private final Semaphore lock = new Semaphore(0);
private final TerminalClient terminal;
private final String url;
private final String user;
private final String password;
public GreenScreenConnector(String url, String user, String password) {
this.url = url;
this.user = user;
this.password = password;
this.terminal = new TerminalClient(0, new ScreenDimensions(24, 80));
terminal.setConnectionTimeoutMillis(20_000);
terminal.setConnectionListener(buildConnectionListener());
terminal.addScreenChangeListener(sw -> {
LOG.debug(() -> "screen changed: \n" + toCp870(terminal.getScreenText()));
lock.release();
});
terminal.setSocketFactory(buildSocketFactory());
}
private static ConnectionListener buildConnectionListener() {
return new ConnectionListener() {
@Override
public void onConnection() {
LOG.info("CONNECTED!!!");
}
@Override
public void onException(Exception e) {
LOG.error("Green screen connection failed", e);
}
@Override
public void onConnectionClosed() {
LOG.info("CONNECTION CLOSED!!!");
}
};
}
private static String toCp870(String cp1047) {
try {
return new String(cp1047.getBytes("CP1047"), "CP870");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Error converting CP1047 to CP870");
}
}
private static String toCp1047(String cp870) {
try {
return new String(cp870.getBytes("CP870"), "CP1047");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Error converting CP870 to CP1047");
}
}
public void connectAndLogin() {
terminal.connect(url, 992);
waitForScreenChange();
terminal.setFieldTextByCoord(24, 1, "atrf");
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
terminal.setFieldTextByCoord(6, 25, CLIENT);
terminal.setFieldTextByCoord(8, 24, user);
terminal.setFieldTextByCoord(10, 24, password);
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
waitForScreenChange();
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
}
private void waitForScreenChange() {
boolean success = false;
try {
success = lock.tryAcquire(5L, TimeUnit.SECONDS);
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
LOG.error(e);
}
if (!success) {
throw new IllegalStateException("Failed to load screen within 5 second timeout");
}
}
private SSLSocketFactory buildSocketFactory() {
try (InputStream truststoreIs = this.getClass().getClassLoader()
.getResourceAsStream("keystores/greenScreenTrustStore")) {
KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustedStore.load(truststoreIs, "changeit".toCharArray());
return SslConfigurator.newInstance()
.trustStore(trustedStore)
.keyStorePassword("changeit".toCharArray())
.createSSLContext()
.getSocketFactory();
} catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) {
throw new IllegalStateException("Failed to initialize SSL context", e);
}
}
public String getText(int row, int column, int length) {
String screenText = toCp870(terminal.getScreenText());
if (length == 0) {
return screenText;
} else {
int beginIndex = (row - 1) * 81 + column - 1;
return screenText.substring(beginIndex, beginIndex + length);
}
}
public void pressKey(Key key) {
switch (key) {
case ENTER:
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange();
break;
default:
throw new IllegalArgumentException("Unknown key " + key.name());
}
}
public void type(int row, int column, String text) {
terminal.setFieldTextByCoord(row, column, toCp1047(text));
}
}

View File

@ -1,129 +1,129 @@
package cz.moneta.test.harness.connectors;
import itSodTesting.sharedResources.objects.dataTransfer.IlodsServerEnvironmentList;
import itSodTesting.sharedResources.objects.dataTransfer.IlodsServerSourceSystem;
import itSodTesting.sharedResources.objects.dataTransfer.ObjectTransferToClient;
import itSodTesting.sharedResources.objects.dataTransfer.ObjectTransferToServer;
import itSodTesting.sharedResources.utils.getUser.LoggedUser;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class IlodsServerConnector implements Connector {
private static final Logger logger = LogManager.getLogger(IlodsServerConnector.class);
private static final String token = "&PE$T.Q;1SXdhF@#$vdnv3,M8r5+dr=e";
private static final Pair<String, String> liveServer = Pair.of("primary", "MBCZVW1DL0LRC02.mbid.cz");
private static final Pair<String, String> backupServer = Pair.of("secondary", "MBCZVW0BL0LRC01.mbid.cz");
private static final Pair<String, String> devServer = Pair.of("develop", "MBCZDWHQXX10366.mbid.cz");
private Pair<String, String> currentServer = liveServer;
// private Pair<String, String> currentServer = devServer;
private IlodsServerEnvironmentList environment;
public IlodsServerConnector(IlodsServerEnvironmentList environment) {
this.environment = environment;
}
public ObjectTransferToClient sendRequest(ObjectTransferToServer data) {
ObjectTransferToClient receivedObject = connectAndReceiveData(data);
// no response = second try
if (receivedObject == null) {
switchServer();
receivedObject = connectAndReceiveData(data);
}
if (receivedObject == null) {
throw new RuntimeException("Cannot connect to Ilods servers");
}
return receivedObject;
}
private ObjectTransferToClient connectAndReceiveData(ObjectTransferToServer data) {
ObjectTransferToClient receivedObject = null;
Socket clientSocket = null;
try {
clientSocket = new Socket();
clientSocket.connect(new InetSocketAddress(currentServer.getRight(), 51001), 1000);
clientSocket.setSoTimeout(60 * 1000);
ObjectOutputStream outToServer = new ObjectOutputStream(clientSocket.getOutputStream());
ObjectInputStream inFromServer = new ObjectInputStream(clientSocket.getInputStream());
// add clients informations do data
data.setToken(token);
data.setSourceSystem(IlodsServerSourceSystem.HARNESS);
data.setMachineId(LoggedUser.getComputerName());
data.setClientId(LoggedUser.getUserName());
data.setEnvironment(environment);
// pack and sending object data to server
outToServer.writeObject(data);
// unpack and reading object response from server
Object transferedObject = inFromServer.readObject();
receivedObject = (ObjectTransferToClient) transferedObject;
// recognize and process errors
if (receivedObject.getResultCode() == 12) {
throw new RuntimeException("Fatal error during processing " + data.getActionType() + ": " + receivedObject.getResultString());
} else if (receivedObject.getResultCode() != 0) {
throw new RuntimeException("Server error during processing " + data.getActionType() + ": " + receivedObject.getResultString());
}
} catch (SocketTimeoutException e) {
logger.warn("Connection timed out - " + currentServer.getLeft() + " Ilods server didn't respond within the specified interval");
} catch (ClassNotFoundException | IOException e) {
logger.warn("Ilods server (" + currentServer.getLeft() + ") unavailable", e);
} finally {
closeConnection(clientSocket);
}
return receivedObject;
}
private void switchServer() {
// Set up switch between primary/secondary Ilods Server (with check develop mode)
if (currentServer == devServer) {
return;
}
if (currentServer == liveServer) {
currentServer = backupServer;
} else {
currentServer = liveServer;
}
logger.info("Trying to call " + currentServer.getLeft() + " Ilods server");
}
private void closeConnection(Socket socket) {
if (socket == null)
return;
try {
socket.shutdownInput();
} catch (IOException e) {
}
try {
socket.shutdownOutput();
} catch (IOException e) {
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package cz.moneta.test.harness.connectors;
import itSodTesting.sharedResources.objects.dataTransfer.IlodsServerEnvironmentList;
import itSodTesting.sharedResources.objects.dataTransfer.IlodsServerSourceSystem;
import itSodTesting.sharedResources.objects.dataTransfer.ObjectTransferToClient;
import itSodTesting.sharedResources.objects.dataTransfer.ObjectTransferToServer;
import itSodTesting.sharedResources.utils.getUser.LoggedUser;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class IlodsServerConnector implements Connector {
private static final Logger logger = LogManager.getLogger(IlodsServerConnector.class);
private static final String token = "&PE$T.Q;1SXdhF@#$vdnv3,M8r5+dr=e";
private static final Pair<String, String> liveServer = Pair.of("primary", "MBCZVW1DL0LRC02.mbid.cz");
private static final Pair<String, String> backupServer = Pair.of("secondary", "MBCZVW0BL0LRC01.mbid.cz");
private static final Pair<String, String> devServer = Pair.of("develop", "MBCZDWHQXX10366.mbid.cz");
private Pair<String, String> currentServer = liveServer;
// private Pair<String, String> currentServer = devServer;
private IlodsServerEnvironmentList environment;
public IlodsServerConnector(IlodsServerEnvironmentList environment) {
this.environment = environment;
}
public ObjectTransferToClient sendRequest(ObjectTransferToServer data) {
ObjectTransferToClient receivedObject = connectAndReceiveData(data);
// no response = second try
if (receivedObject == null) {
switchServer();
receivedObject = connectAndReceiveData(data);
}
if (receivedObject == null) {
throw new RuntimeException("Cannot connect to Ilods servers");
}
return receivedObject;
}
private ObjectTransferToClient connectAndReceiveData(ObjectTransferToServer data) {
ObjectTransferToClient receivedObject = null;
Socket clientSocket = null;
try {
clientSocket = new Socket();
clientSocket.connect(new InetSocketAddress(currentServer.getRight(), 51001), 1000);
clientSocket.setSoTimeout(60 * 1000);
ObjectOutputStream outToServer = new ObjectOutputStream(clientSocket.getOutputStream());
ObjectInputStream inFromServer = new ObjectInputStream(clientSocket.getInputStream());
// add clients informations do data
data.setToken(token);
data.setSourceSystem(IlodsServerSourceSystem.HARNESS);
data.setMachineId(LoggedUser.getComputerName());
data.setClientId(LoggedUser.getUserName());
data.setEnvironment(environment);
// pack and sending object data to server
outToServer.writeObject(data);
// unpack and reading object response from server
Object transferedObject = inFromServer.readObject();
receivedObject = (ObjectTransferToClient) transferedObject;
// recognize and process errors
if (receivedObject.getResultCode() == 12) {
throw new RuntimeException("Fatal error during processing " + data.getActionType() + ": " + receivedObject.getResultString());
} else if (receivedObject.getResultCode() != 0) {
throw new RuntimeException("Server error during processing " + data.getActionType() + ": " + receivedObject.getResultString());
}
} catch (SocketTimeoutException e) {
logger.warn("Connection timed out - " + currentServer.getLeft() + " Ilods server didn't respond within the specified interval");
} catch (ClassNotFoundException | IOException e) {
logger.warn("Ilods server (" + currentServer.getLeft() + ") unavailable", e);
} finally {
closeConnection(clientSocket);
}
return receivedObject;
}
private void switchServer() {
// Set up switch between primary/secondary Ilods Server (with check develop mode)
if (currentServer == devServer) {
return;
}
if (currentServer == liveServer) {
currentServer = backupServer;
} else {
currentServer = liveServer;
}
logger.info("Trying to call " + currentServer.getLeft() + " Ilods server");
}
private void closeConnection(Socket socket) {
if (socket == null)
return;
try {
socket.shutdownInput();
} catch (IOException e) {
}
try {
socket.shutdownOutput();
} catch (IOException e) {
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -1,123 +1,123 @@
package cz.moneta.test.harness.connectors;
import com.bettercloud.vault.SslConfig;
import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.api.Logical;
import com.bettercloud.vault.response.LogicalResponse;
import cz.moneta.test.harness.connectors.rest.BaseRestConnector;
import cz.moneta.test.harness.support.auth.Credentials;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.security.KeyStore;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class VaultConnector extends BaseRestConnector {
private static final Logger LOG = LogManager.getLogger(VaultConnector.class);
private static String url;
private static String username;
private static String pwd;
private static Vault vault;
private static VaultConfig config;
private static LocalDateTime tokenExpiration;
public VaultConnector(String url, String username, String pwd) {
this.url = url;
this.username = username;
this.pwd = pwd;
if (tokenExpiration == null) {
initVaultConnection();
} else if (tokenExpiration.minusMinutes(1).isBefore(LocalDateTime.now())) {
loginVault();
}
}
private void initVaultConnection() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(this.getClass().getClassLoader().getResourceAsStream("keystores/mb_root"), "changeit".toCharArray());
config = new VaultConfig()
.address(url)
.sslConfig(new SslConfig()
.trustStore(keyStore)
.build())
.build();
vault = new Vault(config, 2);
loginVault();
} catch (Exception e) {
throw new IllegalStateException("Failed to connect to vault url: " + url, e);
}
}
private void loginVault() {
try {
String token = vault.auth().loginByUserPass(username, pwd).getAuthClientToken();
config.token(token).build();
long ttl = vault.auth().lookupSelf().getTTL();
tokenExpiration = LocalDateTime.now().plusSeconds(ttl);
} catch (VaultException e) {
LOG.error("Cannot get Vault token", e);
}
}
public Optional<Credentials> getUsernameAndPassword(String path) {
return Optional.ofNullable(vault.logical())
.map(v -> readValue(path, v))
.map(LogicalResponse::getDataObject)
.flatMap(d -> Optional.ofNullable(d.getString("username")).map(usr -> Pair.of(usr, d)))
.flatMap(p -> Optional.ofNullable(p.getRight().getString("password")).map(pwd -> new Credentials(p.getLeft(), pwd)));
}
@SuppressWarnings("unchecked")
public <T> Optional<T> getValue(String path, String key) {
return Optional.ofNullable(vault.logical())
.map(v -> readValue(path, v))
.map(LogicalResponse::getDataObject)
.map(d -> (T) d.getString(key));
}
public void setKeyValue(String path, String key, String value) {
Optional.ofNullable(readValue(path, vault.logical()))
.map(LogicalResponse::getData)
.map(map -> new HashMap<String, Object>(map))
.map(m -> {
m.put(key, value);
return m;
})
.map(m -> writeValue(path, m, vault.logical()))
.orElseThrow(() -> new IllegalStateException("Cannot write value to vault"));
}
private LogicalResponse writeValue(String path, Map<String, Object> value, Logical vault) {
try {
return vault.write(path, value);
} catch (VaultException e) {
LOG.error("Failed to write value from vault: {}\n{}", path, e);
return null;
}
}
private LogicalResponse readValue(String path, Logical v) {
try {
return v.read(path);
} catch (VaultException e) {
LOG.error("Failed to read value from vault: {}\n{}", path, e);
return null;
}
}
}
package cz.moneta.test.harness.connectors;
import com.bettercloud.vault.SslConfig;
import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.api.Logical;
import com.bettercloud.vault.response.LogicalResponse;
import cz.moneta.test.harness.connectors.rest.BaseRestConnector;
import cz.moneta.test.harness.support.auth.Credentials;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.security.KeyStore;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class VaultConnector extends BaseRestConnector {
private static final Logger LOG = LogManager.getLogger(VaultConnector.class);
private static String url;
private static String username;
private static String pwd;
private static Vault vault;
private static VaultConfig config;
private static LocalDateTime tokenExpiration;
public VaultConnector(String url, String username, String pwd) {
this.url = url;
this.username = username;
this.pwd = pwd;
if (tokenExpiration == null) {
initVaultConnection();
} else if (tokenExpiration.minusMinutes(1).isBefore(LocalDateTime.now())) {
loginVault();
}
}
private void initVaultConnection() {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(this.getClass().getClassLoader().getResourceAsStream("keystores/mb_root"), "changeit".toCharArray());
config = new VaultConfig()
.address(url)
.sslConfig(new SslConfig()
.trustStore(keyStore)
.build())
.build();
vault = new Vault(config, 2);
loginVault();
} catch (Exception e) {
throw new IllegalStateException("Failed to connect to vault url: " + url, e);
}
}
private void loginVault() {
try {
String token = vault.auth().loginByUserPass(username, pwd).getAuthClientToken();
config.token(token).build();
long ttl = vault.auth().lookupSelf().getTTL();
tokenExpiration = LocalDateTime.now().plusSeconds(ttl);
} catch (VaultException e) {
LOG.error("Cannot get Vault token", e);
}
}
public Optional<Credentials> getUsernameAndPassword(String path) {
return Optional.ofNullable(vault.logical())
.map(v -> readValue(path, v))
.map(LogicalResponse::getDataObject)
.flatMap(d -> Optional.ofNullable(d.getString("username")).map(usr -> Pair.of(usr, d)))
.flatMap(p -> Optional.ofNullable(p.getRight().getString("password")).map(pwd -> new Credentials(p.getLeft(), pwd)));
}
@SuppressWarnings("unchecked")
public <T> Optional<T> getValue(String path, String key) {
return Optional.ofNullable(vault.logical())
.map(v -> readValue(path, v))
.map(LogicalResponse::getDataObject)
.map(d -> (T) d.getString(key));
}
public void setKeyValue(String path, String key, String value) {
Optional.ofNullable(readValue(path, vault.logical()))
.map(LogicalResponse::getData)
.map(map -> new HashMap<String, Object>(map))
.map(m -> {
m.put(key, value);
return m;
})
.map(m -> writeValue(path, m, vault.logical()))
.orElseThrow(() -> new IllegalStateException("Cannot write value to vault"));
}
private LogicalResponse writeValue(String path, Map<String, Object> value, Logical vault) {
try {
return vault.write(path, value);
} catch (VaultException e) {
LOG.error("Failed to write value from vault: {}\n{}", path, e);
return null;
}
}
private LogicalResponse readValue(String path, Logical v) {
try {
return v.read(path);
} catch (VaultException e) {
LOG.error("Failed to read value from vault: {}\n{}", path, e);
return null;
}
}
}

View File

@ -1,46 +1,46 @@
package cz.moneta.test.harness.connectors;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.ws.BindingProvider;
import jakarta.xml.ws.Dispatch;
import jakarta.xml.ws.Service;
import jakarta.xml.ws.handler.MessageContext;
import org.apache.cxf.jaxws.CXFService;
import javax.xml.namespace.QName;
import java.net.URL;
public class WsConnector {
private final Service service;
private final QName portName;
private final String address;
private final String serviceNamespace;
public WsConnector(String address, URL wsdlUrl, QName serviceName) {
this.address = address;
Service service = CXFService.create(wsdlUrl, serviceName);
if (service.getPorts().hasNext()) {
this.portName = service.getPorts().next();
} else {
throw new IllegalStateException(String.format("Service definition %s has no ports defined", wsdlUrl.toString()));
}
this.serviceNamespace = serviceName.getNamespaceURI();
this.service = service;
}
@SuppressWarnings("unchecked")
public <RESP> RESP invoke(Object request, Class<RESP> responseClass) {
try {
QName operationName = new QName(serviceNamespace, request.getClass().getSimpleName());
JAXBContext jaxbContext = JAXBContext.newInstance(responseClass.getPackage().getName());
Dispatch<Object> dispatch = service.createDispatch(portName, jaxbContext, Service.Mode.PAYLOAD);
dispatch.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, address);
dispatch.getRequestContext().put(MessageContext.WSDL_OPERATION, operationName);
return (RESP) dispatch.invoke(request);
} catch (JAXBException e) {
throw new IllegalArgumentException("Failed to invoke request", e);
}
}
}
package cz.moneta.test.harness.connectors;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.ws.BindingProvider;
import jakarta.xml.ws.Dispatch;
import jakarta.xml.ws.Service;
import jakarta.xml.ws.handler.MessageContext;
import org.apache.cxf.jaxws.CXFService;
import javax.xml.namespace.QName;
import java.net.URL;
public class WsConnector {
private final Service service;
private final QName portName;
private final String address;
private final String serviceNamespace;
public WsConnector(String address, URL wsdlUrl, QName serviceName) {
this.address = address;
Service service = CXFService.create(wsdlUrl, serviceName);
if (service.getPorts().hasNext()) {
this.portName = service.getPorts().next();
} else {
throw new IllegalStateException(String.format("Service definition %s has no ports defined", wsdlUrl.toString()));
}
this.serviceNamespace = serviceName.getNamespaceURI();
this.service = service;
}
@SuppressWarnings("unchecked")
public <RESP> RESP invoke(Object request, Class<RESP> responseClass) {
try {
QName operationName = new QName(serviceNamespace, request.getClass().getSimpleName());
JAXBContext jaxbContext = JAXBContext.newInstance(responseClass.getPackage().getName());
Dispatch<Object> dispatch = service.createDispatch(portName, jaxbContext, Service.Mode.PAYLOAD);
dispatch.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, address);
dispatch.getRequestContext().put(MessageContext.WSDL_OPERATION, operationName);
return (RESP) dispatch.invoke(request);
} catch (JAXBException e) {
throw new IllegalArgumentException("Failed to invoke request", e);
}
}
}

View File

@ -1,27 +1,27 @@
package cz.moneta.test.harness.connectors.common;
import cz.moneta.test.harness.config.ConfigProvider;
import cz.moneta.test.harness.context.ConfigAccessor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ServletConfigAccessor implements ConfigAccessor {
private final Map<String, String> config = new ConcurrentHashMap<>(ConfigProvider.readConfig());
@Override
public String getConfig(String key) {
return config.get(key);
}
@Override
public String getConfig(String key, String defaultValue) {
return config.getOrDefault(key, defaultValue);
}
@Override
public void putConfig(String key, String value) {
config.put(key, value);
}
}
package cz.moneta.test.harness.connectors.common;
import cz.moneta.test.harness.config.ConfigProvider;
import cz.moneta.test.harness.context.ConfigAccessor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ServletConfigAccessor implements ConfigAccessor {
private final Map<String, String> config = new ConcurrentHashMap<>(ConfigProvider.readConfig());
@Override
public String getConfig(String key) {
return config.get(key);
}
@Override
public String getConfig(String key, String defaultValue) {
return config.getOrDefault(key, defaultValue);
}
@Override
public void putConfig(String key, String value) {
config.put(key, value);
}
}

View File

@ -1,44 +1,44 @@
package cz.moneta.test.harness.connectors.database;
import cz.moneta.test.harness.connectors.Connector;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DatabaseConnector implements Connector {
private final String url;
private final String user;
private final String password;
public DatabaseConnector(String url, String user, String password) {
this.url = url;
this.user = user;
this.password = password;
}
protected Result<Record> executeSql(String sql, SQLDialect sqlDialect) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
return DSL.using(connection, sqlDialect).fetch(sql);
} catch (SQLException e) {
throw new RuntimeException("Error executing native sql database", e);
}
}
public String getUrl() {
return url;
}
public String getUser() {
return user;
}
public String getPassword() {
return password;
}
}
package cz.moneta.test.harness.connectors.database;
import cz.moneta.test.harness.connectors.Connector;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DatabaseConnector implements Connector {
private final String url;
private final String user;
private final String password;
public DatabaseConnector(String url, String user, String password) {
this.url = url;
this.user = user;
this.password = password;
}
protected Result<Record> executeSql(String sql, SQLDialect sqlDialect) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
return DSL.using(connection, sqlDialect).fetch(sql);
} catch (SQLException e) {
throw new RuntimeException("Error executing native sql database", e);
}
}
public String getUrl() {
return url;
}
public String getUser() {
return user;
}
public String getPassword() {
return password;
}
}

View File

@ -1,23 +1,23 @@
package cz.moneta.test.harness.connectors.database;
import cz.moneta.test.harness.support.auth.Credentials;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
public class MsSqlConnector extends DatabaseConnector {
private static final String windowsAuthenticationOption = ";integratedSecurity=true";
public MsSqlConnector(String url, Credentials credentials) {
super(url, credentials.getUsername(), credentials.getPassword());
}
public MsSqlConnector(String url) {
super(url + windowsAuthenticationOption, "", "");
}
public Result<Record> executeSql(String sql) {
return super.executeSql(sql, SQLDialect.SQLSERVER);
}
package cz.moneta.test.harness.connectors.database;
import cz.moneta.test.harness.support.auth.Credentials;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
public class MsSqlConnector extends DatabaseConnector {
private static final String windowsAuthenticationOption = ";integratedSecurity=true";
public MsSqlConnector(String url, Credentials credentials) {
super(url, credentials.getUsername(), credentials.getPassword());
}
public MsSqlConnector(String url) {
super(url + windowsAuthenticationOption, "", "");
}
public Result<Record> executeSql(String sql) {
return super.executeSql(sql, SQLDialect.SQLSERVER);
}
}

View File

@ -1,65 +1,65 @@
package cz.moneta.test.harness.connectors.database;
import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.Routine;
import org.jooq.SQLDialect;
import org.jooq.TableRecord;
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultConnectionProvider;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.function.Consumer;
import java.util.function.Function;
public class OracleConnector extends DatabaseConnector {
public OracleConnector(String url, String user, String password) {
super(url, user, password);
}
public <R extends TableRecord<R>> R executeDsl(Function<DSLContext, R> query) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
return query.apply(DSL.using(connection, SQLDialect.ORACLE));
} catch (SQLException e) {
throw new RuntimeException("Error querying database", e);
}
}
public <K> K insertDsl(Function<DSLContext, K> query) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
return query.apply(DSL.using(connection, SQLDialect.ORACLE));
} catch (SQLException e) {
throw new RuntimeException("Error querying database", e);
}
}
public <R extends Routine<?>> R executeProcedure(R procedure) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
Configuration configuration = new DefaultConfiguration()
.derive(new DefaultConnectionProvider(connection))
.derive(SQLDialect.ORACLE);
procedure.execute(configuration);
return procedure;
} catch (SQLException e) {
throw new RuntimeException("Error executing procedure database", e);
}
}
public Result<Record> executeSql(String sql) {
return super.executeSql(sql, SQLDialect.ORACLE);
}
public void executeStatement(Consumer<Connection> execute) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
execute.accept(connection);
} catch (SQLException e) {
throw new RuntimeException("Error executing statement", e);
}
}
}
package cz.moneta.test.harness.connectors.database;
import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.Routine;
import org.jooq.SQLDialect;
import org.jooq.TableRecord;
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultConnectionProvider;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.function.Consumer;
import java.util.function.Function;
public class OracleConnector extends DatabaseConnector {
public OracleConnector(String url, String user, String password) {
super(url, user, password);
}
public <R extends TableRecord<R>> R executeDsl(Function<DSLContext, R> query) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
return query.apply(DSL.using(connection, SQLDialect.ORACLE));
} catch (SQLException e) {
throw new RuntimeException("Error querying database", e);
}
}
public <K> K insertDsl(Function<DSLContext, K> query) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
return query.apply(DSL.using(connection, SQLDialect.ORACLE));
} catch (SQLException e) {
throw new RuntimeException("Error querying database", e);
}
}
public <R extends Routine<?>> R executeProcedure(R procedure) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
Configuration configuration = new DefaultConfiguration()
.derive(new DefaultConnectionProvider(connection))
.derive(SQLDialect.ORACLE);
procedure.execute(configuration);
return procedure;
} catch (SQLException e) {
throw new RuntimeException("Error executing procedure database", e);
}
}
public Result<Record> executeSql(String sql) {
return super.executeSql(sql, SQLDialect.ORACLE);
}
public void executeStatement(Consumer<Connection> execute) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
execute.accept(connection);
} catch (SQLException e) {
throw new RuntimeException("Error executing statement", e);
}
}
}

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.connectors.database;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
public class PostgresConnector extends DatabaseConnector {
public PostgresConnector(String url, String user, String password) {
super(url, user, password);
}
public Result<Record> executeSql(String sqlToExecute) {
return super.executeSql(sqlToExecute, SQLDialect.POSTGRES);
}
}
package cz.moneta.test.harness.connectors.database;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
public class PostgresConnector extends DatabaseConnector {
public PostgresConnector(String url, String user, String password) {
super(url, user, password);
}
public Result<Record> executeSql(String sqlToExecute) {
return super.executeSql(sqlToExecute, SQLDialect.POSTGRES);
}
}

View File

@ -1,24 +1,23 @@
package cz.moneta.test.harness.connectors.messaging;
import java.io.FileInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.jms.BytesMessage;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.JMSRuntimeException;
import javax.jms.Message;
import javax.jms.TextMessage;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
@ -27,26 +26,28 @@ import javax.net.ssl.TrustManagerFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.ibm.mq.jms.MQConnectionFactory;
import com.ibm.mq.MQException;
import com.ibm.mq.MQGetMessageOptions;
import com.ibm.mq.MQMessage;
import com.ibm.mq.MQPutMessageOptions;
import com.ibm.mq.MQQueue;
import com.ibm.mq.MQQueueManager;
import com.ibm.mq.constants.CMQC;
import com.ibm.msg.client.wmq.WMQConstants;
import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.messaging.MessageContentType;
import cz.moneta.test.harness.messaging.MqMessageFormat;
import cz.moneta.test.harness.messaging.ReceivedMessage;
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
import cz.moneta.test.harness.messaging.exception.MessagingDestinationException;
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
import cz.moneta.test.harness.support.messaging.ImqRequest;
import cz.moneta.test.harness.support.messaging.MessageContentType;
import cz.moneta.test.harness.support.messaging.MqMessageFormat;
import cz.moneta.test.harness.support.messaging.ReceivedMessage;
import cz.moneta.test.harness.support.messaging.exception.MessagingConnectionException;
import cz.moneta.test.harness.support.messaging.exception.MessagingDestinationException;
import cz.moneta.test.harness.support.messaging.exception.MessagingTimeoutException;
/**
* IBM MQ connector using JMS client with Jakarta JMS API. Supports
* multi-instance Queue Manager, SSL/TLS, and multiple message formats.
* <p>
* Supported formats: - JSON: JMS TextMessage with plain JSON string (default) -
* XML: JMS TextMessage with XML string - UTF-8 (CCSID 1208): JMS BytesMessage
* with UTF-8 encoding - EBCDIC (CCSID 870): JMS BytesMessage with EBCDIC
* IBM-870 encoding
* IBM MQ connector using the native com.ibm.mq classes. Supports
* multi-instance Queue Manager connection lists, SSL/TLS, message properties,
* and JSON/XML/UTF-8/EBCDIC payload formats.
*/
public class IbmMqConnector implements Connector {
@ -59,12 +60,19 @@ public class IbmMqConnector implements Connector {
private static final long DEFAULT_MAX_POLL_INTERVAL_MS = 1000;
private static final String TLS_VERSION = "TLSv1.2";
private static final String JMS_ID_PREFIX = "ID:";
private static final Pattern SELECTOR_EQUALS_PATTERN = Pattern
.compile("\\s*([A-Za-z_][A-Za-z0-9_.-]*)\\s*=\\s*'((?:''|[^'])*)'\\s*");
private final MQConnectionFactory connectionFactory;
private JMSContext jmsContext;
private final String connectionNameList;
private final String channel;
private final String queueManager;
private final String user;
private final String password;
private final SSLSocketFactory sslSocketFactory;
private final String sslCipherSuite;
private MQQueueManager mqQueueManager;
/**
* Constructor with multi-instance Queue Manager support.
@ -82,35 +90,17 @@ public class IbmMqConnector implements Connector {
*/
public IbmMqConnector(String connectionNameList, String channel, String queueManager, String user, String password,
String keystorePath, String keystorePassword, String sslCipherSuite) {
this.connectionNameList = connectionNameList;
this.channel = channel;
this.queueManager = queueManager;
this.user = user;
this.password = password;
this.sslCipherSuite = sslCipherSuite;
try {
connectionFactory = new MQConnectionFactory();
connectionFactory.setConnectionNameList(connectionNameList);
connectionFactory.setQueueManager(queueManager);
connectionFactory.setChannel(channel);
connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
if (user != null && !user.isBlank()) {
connectionFactory.setStringProperty(WMQConstants.USERID, user);
}
if (password != null && !password.isBlank()) {
connectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
}
if (keystorePath != null && !keystorePath.isBlank() && keystorePassword != null
&& !keystorePassword.isBlank()) {
connectionFactory.setSSLSocketFactory(getSslSocketFactory(keystorePath, keystorePassword));
}
if (sslCipherSuite != null && !sslCipherSuite.isBlank()) {
connectionFactory.setSSLCipherSuite(sslCipherSuite);
}
// Initialize JMS context
this.sslSocketFactory = keystorePath != null && !keystorePath.isBlank() && keystorePassword != null
&& !keystorePassword.isBlank() ? getSslSocketFactory(keystorePath, keystorePassword) : null;
connect();
} catch (Exception e) {
throw new MessagingConnectionException("Failed to create IBM MQ connection to " + queueManager, e);
}
@ -121,97 +111,82 @@ public class IbmMqConnector implements Connector {
*/
private void connect() {
try {
this.jmsContext = connectionFactory.createContext(user, password, JMSContext.AUTO_ACKNOWLEDGE);
this.jmsContext.start();
this.mqQueueManager = new MQQueueManager(queueManager, createConnectionProperties());
LOG.info("Connected to IBM MQ: {}", queueManager);
} catch (Exception e) {
} catch (MQException e) {
throw new MessagingConnectionException(
"Failed to connect to IBM MQ: " + queueManager + " - " + e.getMessage(), e);
}
}
/**
* Send a JSON or XML message as TextMessage.
*/
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) {
javax.jms.Queue queue = getQueue(queueName);
private Hashtable<String, Object> createConnectionProperties() {
Hashtable<String, Object> properties = new Hashtable<>();
if (connectionNameList != null && !connectionNameList.isBlank()) {
properties.put(WMQConstants.WMQ_CONNECTION_NAME_LIST, connectionNameList);
}
properties.put(CMQC.CHANNEL_PROPERTY, channel);
properties.put(CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES_CLIENT);
TextMessage message = jmsContext.createTextMessage(payload);
// Set JMS properties
if (properties != null) {
for (Map.Entry<String, String> entry : properties.entrySet()) {
try {
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) {
message.setJMSCorrelationID(entry.getValue());
continue;
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_TYPE)) {
message.setJMSType(entry.getValue());
continue;
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_MESSAGE_ID)) {
message.setJMSMessageID(entry.getValue());
continue;
}
message.setStringProperty(entry.getKey(), entry.getValue());
} catch (JMSException e) {
LOG.warn("Failed to set property: {}", entry.getKey(), e);
}
}
if (user != null && !user.isBlank()) {
properties.put(CMQC.USER_ID_PROPERTY, user);
}
if (password != null && !password.isBlank()) {
properties.put(CMQC.PASSWORD_PROPERTY, password);
}
if (sslSocketFactory != null) {
properties.put(CMQC.SSL_SOCKET_FACTORY_PROPERTY, sslSocketFactory);
}
if (sslCipherSuite != null && !sslCipherSuite.isBlank()) {
properties.put(CMQC.SSL_CIPHER_SUITE_PROPERTY, sslCipherSuite);
}
try {
jmsContext.createProducer().send(queue, message);
} catch (RuntimeException e) {
throw new MessagingDestinationException("Failed to send message to queue: " + queueName, e);
}
LOG.debug("Sent JSON/XML message to queue: {}", queueName);
return properties;
}
/**
* Send a message as BytesMessage with specific encoding and CCSID.
* Send a JSON or XML message as MQ string message.
*/
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) {
sendMessage(queueName, payload.getBytes(UTF_8), UTF_8, 1208, CMQC.MQFMT_STRING, properties, "JSON/XML");
}
/**
* Send a message as raw bytes with specific encoding and CCSID.
*/
private void sendBytesMessage(String queueName, String payload, Charset charset, int ccsid,
Map<String, String> properties) {
javax.jms.Queue queue = getQueue(queueName);
sendMessage(queueName, payload.getBytes(charset), charset, ccsid, CMQC.MQFMT_NONE, properties,
charset.displayName());
}
BytesMessage message = jmsContext.createBytesMessage();
// Convert payload to bytes using specified charset
byte[] bytes = payload.getBytes(charset);
private void sendMessage(String queueName, byte[] payload, Charset charset, int ccsid, String mqFormat,
Map<String, String> properties, String logFormat) {
MQQueue queue = null;
try {
message.writeBytes(bytes);
message.setIntProperty("CCSID", ccsid);
} catch (JMSException e) {
throw new MessagingDestinationException("Failed to create bytes message", e);
}
int openOptions = CMQC.MQOO_OUTPUT | CMQC.MQOO_FAIL_IF_QUIESCING;
// Set JMS properties
if (properties != null) {
for (Map.Entry<String, String> entry : properties.entrySet()) {
try {
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) {
message.setJMSCorrelationID(entry.getValue());
continue;
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_TYPE)) {
message.setJMSType(entry.getValue());
continue;
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_MESSAGE_ID)) {
message.setJMSMessageID(entry.getValue());
continue;
}
message.setStringProperty(entry.getKey(), entry.getValue());
} catch (JMSException e) {
LOG.warn("Failed to set property: {}", entry.getKey(), e);
}
queue = openQueue(queueName, openOptions);
MQMessage message = new MQMessage();
message.format = mqFormat;
message.characterSet = ccsid;
message.write(payload);
if (!CMQC.MQFMT_STRING.equals(mqFormat)) {
message.setIntProperty("CCSID", ccsid);
}
MQPutMessageOptions putOptions = new MQPutMessageOptions();
putOptions.options = CMQC.MQPMO_NO_SYNCPOINT | CMQC.MQPMO_FAIL_IF_QUIESCING;
applyMessageProperties(message, properties);
queue.put(message, putOptions);
} catch (MQException | IOException e) {
throw new MessagingDestinationException("Failed to send message to queue: " + queueName, e);
} finally {
closeQueue(queue, queueName);
}
try {
jmsContext.createProducer().send(queue, message);
} catch (RuntimeException e) {
throw new MessagingDestinationException("Failed to send message to queue: " + queueName, e);
}
LOG.debug("Sent {} message to queue: {}", charset, queueName);
LOG.debug("Sent {} message ({}) to queue: {}", logFormat, charset, queueName);
}
/**
@ -220,7 +195,7 @@ public class IbmMqConnector implements Connector {
* @param queueName Queue name
* @param payload Message payload
* @param format Message format (JSON, XML, EBCDIC_870, UTF8_1208)
* @param properties JMS properties to set
* @param properties message properties to set
*/
public void send(String queueName, String payload, MqMessageFormat format, Map<String, String> properties) {
switch (format) {
@ -234,56 +209,99 @@ public class IbmMqConnector implements Connector {
* Receive a message from a queue with timeout.
*
* @param queueName Queue name
* @param messageSelector JMS message selector (optional)
* @param messageSelector JMS-style message selector (optional). Equality
* predicates joined by AND are evaluated client-side.
* @param format Expected message format
* @param timeout Timeout duration
* @return Received message
*/
public ReceivedMessage receive(String queueName, String messageSelector, MqMessageFormat format,
java.time.Duration timeout) {
long timeoutMs = timeout.toMillis();
public ReceivedMessage receive(String queueName, String messageSelector, MqMessageFormat format, Duration timeout) {
if (messageSelector == null || messageSelector.isBlank()) {
return receiveNext(queueName, format, timeout);
}
javax.jms.Queue queue = getQueue(queueName);
JMSConsumer consumer = (messageSelector == null || messageSelector.isBlank() ? jmsContext.createConsumer(queue)
: jmsContext.createConsumer(queue, messageSelector));
AtomicBoolean messageFound = new AtomicBoolean(false);
ReceivedMessage received = null;
long pollInterval = DEFAULT_POLL_INTERVAL_MS;
long remainingTime = timeoutMs;
return receiveMatching(queueName, messageSelector, format, timeout);
}
private ReceivedMessage receiveNext(String queueName, MqMessageFormat format, Duration timeout) {
MQQueue queue = null;
try {
while (remainingTime > 0 && !messageFound.get()) {
Message message = consumer.receive(remainingTime);
queue = openQueue(queueName, CMQC.MQOO_INPUT_SHARED | CMQC.MQOO_FAIL_IF_QUIESCING);
MQMessage message = new MQMessage();
MQGetMessageOptions getOptions = new MQGetMessageOptions();
getOptions.options = CMQC.MQGMO_WAIT | CMQC.MQGMO_NO_SYNCPOINT | CMQC.MQGMO_FAIL_IF_QUIESCING;
getOptions.waitInterval = toWaitInterval(timeout.toMillis());
if (message != null) {
received = decodeMessage(message, queueName, format);
messageFound.set(true);
} else {
// Exponential backoff
pollInterval = Math.min(pollInterval * 2, DEFAULT_MAX_POLL_INTERVAL_MS);
remainingTime -= pollInterval;
}
}
if (received == null) {
queue.get(message, getOptions);
return decodeMessage(message, queueName, format);
} catch (MQException e) {
if (e.reasonCode == CMQC.MQRC_NO_MSG_AVAILABLE) {
throw new MessagingTimeoutException("No message matching filter found on queue '" + queueName
+ "' within " + timeout.toMillis() + "ms");
}
return received;
} catch (MessagingTimeoutException e) {
throw e;
} catch (Exception e) {
throw new MessagingDestinationException("Failed to receive message from queue: " + queueName, e);
} finally {
try {
consumer.close();
} catch (JMSRuntimeException e) {
LOG.warn("Failed to close consumer", e);
closeQueue(queue, queueName);
}
}
private ReceivedMessage receiveMatching(String queueName, String messageSelector, MqMessageFormat format,
Duration timeout) {
long deadline = System.currentTimeMillis() + timeout.toMillis();
long pollInterval = DEFAULT_POLL_INTERVAL_MS;
Selector selector = Selector.parse(messageSelector);
while (System.currentTimeMillis() < deadline) {
ReceivedMessage received = tryReceiveMatching(queueName, format, selector);
if (received != null) {
return received;
}
sleepQuietly(Math.min(pollInterval, Math.max(1, deadline - System.currentTimeMillis())));
pollInterval = Math.min(pollInterval * 2, DEFAULT_MAX_POLL_INTERVAL_MS);
}
throw new MessagingTimeoutException("No message matching filter found on queue '" + queueName + "' within "
+ timeout.toMillis() + "ms");
}
private ReceivedMessage tryReceiveMatching(String queueName, MqMessageFormat format, Selector selector) {
MQQueue queue = null;
try {
queue = openQueue(queueName,
CMQC.MQOO_BROWSE | CMQC.MQOO_INPUT_SHARED | CMQC.MQOO_FAIL_IF_QUIESCING);
MQGetMessageOptions browseOptions = new MQGetMessageOptions();
browseOptions.options = CMQC.MQGMO_BROWSE_FIRST | CMQC.MQGMO_NO_SYNCPOINT | CMQC.MQGMO_FAIL_IF_QUIESCING;
while (true) {
MQMessage browsedMessage = new MQMessage();
try {
queue.get(browsedMessage, browseOptions);
} catch (MQException e) {
if (e.reasonCode == CMQC.MQRC_NO_MSG_AVAILABLE) {
return null;
}
throw e;
}
ReceivedMessage browsed = decodeMessage(browsedMessage, queueName, format);
if (selector.matches(browsed.getHeaders())) {
MQMessage removedMessage = new MQMessage();
MQGetMessageOptions removeOptions = new MQGetMessageOptions();
removeOptions.options = CMQC.MQGMO_MSG_UNDER_CURSOR | CMQC.MQGMO_NO_SYNCPOINT
| CMQC.MQGMO_FAIL_IF_QUIESCING;
queue.get(removedMessage, removeOptions);
return decodeMessage(removedMessage, queueName, format);
}
browseOptions.options = CMQC.MQGMO_BROWSE_NEXT | CMQC.MQGMO_NO_SYNCPOINT
| CMQC.MQGMO_FAIL_IF_QUIESCING;
}
} catch (MQException | RuntimeException e) {
throw new MessagingDestinationException("Failed to receive message from queue: " + queueName, e);
} finally {
closeQueue(queue, queueName);
}
}
@ -291,7 +309,8 @@ public class IbmMqConnector implements Connector {
* Browse a queue (non-destructive read).
*
* @param queueName Queue name
* @param messageSelector JMS message selector (optional)
* @param messageSelector JMS-style message selector (optional). Equality
* predicates joined by AND are evaluated client-side.
* @param format Expected message format
* @param maxMessages Maximum number of messages to browse
* @return List of received messages
@ -299,170 +318,268 @@ public class IbmMqConnector implements Connector {
public List<ReceivedMessage> browse(String queueName, String messageSelector, MqMessageFormat format,
int maxMessages) {
List<ReceivedMessage> messages = new ArrayList<>();
javax.jms.Queue queue = getQueue(queueName);
Selector selector = Selector.parse(messageSelector);
MQQueue queue = null;
try (javax.jms.QueueBrowser browser = (messageSelector == null || messageSelector.isBlank())
? jmsContext.createBrowser(queue)
: jmsContext.createBrowser(queue, messageSelector)) {
try {
queue = openQueue(queueName, CMQC.MQOO_BROWSE | CMQC.MQOO_FAIL_IF_QUIESCING);
Enumeration<?> enumeration = browser.getEnumeration();
int count = 0;
MQGetMessageOptions browseOptions = new MQGetMessageOptions();
browseOptions.options = CMQC.MQGMO_BROWSE_FIRST | CMQC.MQGMO_NO_SYNCPOINT | CMQC.MQGMO_FAIL_IF_QUIESCING;
while (enumeration.hasMoreElements() && count < maxMessages) {
Message message = (Message) enumeration.nextElement();
if (message != null) {
ReceivedMessage received = decodeMessage(message, queueName, format);
messages.add(received);
count++;
while (messages.size() < maxMessages) {
MQMessage message = new MQMessage();
try {
queue.get(message, browseOptions);
} catch (MQException e) {
if (e.reasonCode == CMQC.MQRC_NO_MSG_AVAILABLE) {
break;
}
throw e;
}
ReceivedMessage received = decodeMessage(message, queueName, format);
if (selector.matches(received.getHeaders())) {
messages.add(received);
}
browseOptions.options = CMQC.MQGMO_BROWSE_NEXT | CMQC.MQGMO_NO_SYNCPOINT
| CMQC.MQGMO_FAIL_IF_QUIESCING;
}
return messages;
} catch (JMSException e) {
} catch (MQException e) {
throw new MessagingDestinationException("Failed to browse queue: " + queueName, e);
} finally {
closeQueue(queue, queueName);
}
}
/**
* Decode a JMS message to ReceivedMessage.
* Decode an IBM MQ message to ReceivedMessage.
*/
private ReceivedMessage decodeMessage(Message jmsMessage, String queueName, MqMessageFormat format) {
long timestamp;
try {
timestamp = jmsMessage.getJMSTimestamp();
} catch (JMSException e) {
timestamp = System.currentTimeMillis();
}
private ReceivedMessage decodeMessage(MQMessage mqMessage, String queueName, MqMessageFormat format) {
long timestamp = mqMessage.putDateTime != null ? mqMessage.putDateTime.getTimeInMillis()
: System.currentTimeMillis();
if (timestamp == 0) {
timestamp = System.currentTimeMillis();
}
Map<String, String> headers = new HashMap<>();
extractMqHeadersAndProperties(mqMessage, headers, queueName);
byte[] data = readMessageBody(mqMessage);
String body;
MessageContentType contentType;
Map<String, String> headers = new HashMap<>();
// Extract JMS properties as headers
extractJmsProperties(jmsMessage, headers);
if (jmsMessage instanceof TextMessage textMessage) {
try {
body = textMessage.getText();
} catch (JMSException e) {
throw new RuntimeException("Failed to read text message body", e);
}
contentType = switch (format) {
case XML -> MessageContentType.XML;
default -> MessageContentType.JSON;
};
} else if (jmsMessage instanceof BytesMessage bytesMessage) {
int ccsid;
try {
ccsid = bytesMessage.getIntProperty("CCSID");
} catch (JMSException e) {
ccsid = 1208; // default UTF-8
}
body = decodeBytesMessage(bytesMessage, ccsid);
switch (format) {
case XML -> {
body = new String(data, charsetFor(mqMessage.characterSet, UTF_8));
contentType = MessageContentType.XML;
}
case JSON -> {
body = new String(data, charsetFor(mqMessage.characterSet, UTF_8));
contentType = MessageContentType.JSON;
}
case EBCDIC_870 -> {
body = new String(data, EBCDIC_870);
contentType = MessageContentType.RAW_TEXT;
} else {
try {
throw new IllegalArgumentException("Unsupported message type: " + jmsMessage.getJMSType());
} catch (JMSException e) {
throw new IllegalArgumentException("Unsupported message type", e);
}
}
case UTF8_1208 -> {
body = new String(data, UTF_8);
contentType = MessageContentType.RAW_TEXT;
}
default -> {
body = new String(data, UTF_8);
contentType = MessageContentType.RAW_TEXT;
}
}
return new ReceivedMessage(body, contentType, headers, timestamp, queueName, null);
}
/**
* Decode BytesMessage body based on CCSID.
*/
private String decodeBytesMessage(BytesMessage bytesMessage, int ccsid) {
private byte[] readMessageBody(MQMessage message) {
try {
long bodyLength;
try {
bodyLength = bytesMessage.getBodyLength();
} catch (JMSException e) {
throw new RuntimeException("Failed to get message body length", e);
}
byte[] data = new byte[(int) bodyLength];
bytesMessage.readBytes(data);
Charset charset = switch (ccsid) {
case 870 -> EBCDIC_870;
case 1208 -> UTF_8;
default -> UTF_8;
};
return new String(data, charset);
} catch (JMSException e) {
throw new RuntimeException("Failed to read BytesMessage body", e);
message.seek(0);
int length = message.getMessageLength();
byte[] data = new byte[length];
message.readFully(data);
return data;
} catch (EOFException e) {
throw new RuntimeException("Failed to seek message body", e);
} catch (IOException e) {
throw new RuntimeException("Failed to read message body", e);
}
}
/**
* Extract JMS properties as headers.
* Extract MQ headers and message properties as headers.
*/
@SuppressWarnings("unchecked")
private void extractJmsProperties(Message message, Map<String, String> headers) {
try {
// Common JMS headers
headers.put("JMSMessageID", message.getJMSMessageID());
try {
headers.put("JMSType", message.getJMSType() != null ? message.getJMSType() : "");
} catch (JMSException e) {
headers.put("JMSType", "");
}
try {
headers.put("JMSDestination",
message.getJMSDestination() != null ? message.getJMSDestination().toString() : "");
} catch (JMSException e) {
headers.put("JMSDestination", "");
}
try {
headers.put("JMSDeliveryMode", String.valueOf(message.getJMSDeliveryMode()));
} catch (JMSException e) {
headers.put("JMSDeliveryMode", "");
}
try {
headers.put("JMSPriority", String.valueOf(message.getJMSPriority()));
} catch (JMSException e) {
headers.put("JMSPriority", "");
}
try {
headers.put("JMSTimestamp", String.valueOf(message.getJMSTimestamp()));
} catch (JMSException e) {
headers.put("JMSTimestamp", "");
}
private void extractMqHeadersAndProperties(MQMessage message, Map<String, String> headers, String queueName) {
headers.put("JMSMessageID", toJmsId(message.messageId));
headers.put("JMSCorrelationID", toJmsId(message.correlationId));
headers.put("JMSType", getStringProperty(message, ImqRequest.PROP_JMS_TYPE, ""));
headers.put("JMSDestination", queueName);
headers.put("JMSDeliveryMode", String.valueOf(message.persistence));
headers.put("JMSPriority", String.valueOf(message.priority));
headers.put("JMSTimestamp",
message.putDateTime != null ? String.valueOf(message.putDateTime.getTimeInMillis()) : "");
headers.put("MQFormat", message.format != null ? message.format.trim() : "");
headers.put("MQCharacterSet", String.valueOf(message.characterSet));
headers.put("MQEncoding", String.valueOf(message.encoding));
headers.put("MQBackoutCount", String.valueOf(message.backoutCount));
headers.put("MQReplyToQueue", message.replyToQueueName != null ? message.replyToQueueName.trim() : "");
headers.put("MQReplyToQueueManager",
message.replyToQueueManagerName != null ? message.replyToQueueManagerName.trim() : "");
headers.put("MQUserId", message.userId != null ? message.userId.trim() : "");
// Extract custom properties
Enumeration<String> propertyNames = (Enumeration<String>) message.getPropertyNames();
while (propertyNames.hasMoreElements()) {
String propName = propertyNames.nextElement();
Enumeration<String> propertyNames;
try {
propertyNames = message.getPropertyNames("%");
} catch (MQException e) {
LOG.warn("Failed to extract MQ properties", e);
return;
}
while (propertyNames.hasMoreElements()) {
String propName = propertyNames.nextElement();
try {
Object propValue = message.getObjectProperty(propName);
if (propValue != null) {
headers.put(propName, propValue.toString());
}
} catch (MQException e) {
LOG.warn("Failed to extract MQ property: {}", propName, e);
}
} catch (JMSException e) {
LOG.warn("Failed to extract JMS properties", e);
}
}
/**
* Get Queue object from queue name.
*/
private javax.jms.Queue getQueue(String queueName) {
return jmsContext.createQueue(queueName);
private void applyMessageProperties(MQMessage message, Map<String, String> properties) {
if (properties == null) {
return;
}
for (Map.Entry<String, String> entry : properties.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
try {
if (ImqRequest.PROP_JMS_CORRELATION_ID.equals(key)) {
message.correlationId = toMqId(value);
message.setStringProperty(key, value);
} else if (ImqRequest.PROP_JMS_MESSAGE_ID.equals(key)) {
message.messageId = toMqId(value);
} else if (ImqRequest.PROP_JMS_TYPE.equals(key)) {
message.setStringProperty(ImqRequest.PROP_JMS_TYPE, value);
} else {
message.setStringProperty(key, value);
}
} catch (MQException e) {
LOG.warn("Failed to set property: {}", key, e);
}
}
}
private String getStringProperty(MQMessage message, String property, String defaultValue) {
try {
String value = message.getStringProperty(property);
return value != null ? value : defaultValue;
} catch (MQException e) {
return defaultValue;
}
}
private MQQueue openQueue(String queueName, int openOptions) throws MQException {
ensureConnected();
return mqQueueManager.accessQueue(queueName, openOptions);
}
private void ensureConnected() {
if (mqQueueManager == null || !mqQueueManager.isConnected()) {
connect();
}
}
private void closeQueue(MQQueue queue, String queueName) {
if (queue == null) {
return;
}
try {
queue.close();
} catch (MQException e) {
LOG.warn("Failed to close IBM MQ queue: {}", queueName, e);
}
}
private int toWaitInterval(long timeoutMs) {
if (timeoutMs <= 0) {
return 0;
}
return timeoutMs > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) timeoutMs;
}
private Charset charsetFor(int ccsid, Charset defaultCharset) {
return switch (ccsid) {
case 870 -> EBCDIC_870;
case 1208 -> UTF_8;
default -> defaultCharset;
};
}
private byte[] toMqId(String value) {
if (value == null || value.isBlank()) {
return CMQC.MQMI_NONE.clone();
}
if (value.regionMatches(true, 0, JMS_ID_PREFIX, 0, JMS_ID_PREFIX.length())) {
String hex = value.substring(JMS_ID_PREFIX.length());
if (hex.length() == CMQC.MQ_MSG_ID_LENGTH * 2 && hex.matches("[0-9A-Fa-f]+")) {
return hexToBytes(hex);
}
}
byte[] id = CMQC.MQMI_NONE.clone();
byte[] source = value.getBytes(UTF_8);
System.arraycopy(source, 0, id, 0, Math.min(source.length, id.length));
return id;
}
private String toJmsId(byte[] value) {
if (value == null || value.length == 0 || Arrays.equals(value, CMQC.MQMI_NONE)) {
return "";
}
StringBuilder builder = new StringBuilder(JMS_ID_PREFIX);
for (byte b : value) {
builder.append(String.format(Locale.ROOT, "%02X", b));
}
return builder.toString();
}
private byte[] hexToBytes(String hex) {
byte[] data = new byte[hex.length() / 2];
for (int i = 0; i < data.length; i++) {
int index = i * 2;
data[i] = (byte) Integer.parseInt(hex.substring(index, index + 2), 16);
}
return data;
}
private void sleepQuietly(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new MessagingDestinationException("Interrupted while waiting for IBM MQ message", e);
}
}
@Override
public void close() {
if (jmsContext != null) {
if (mqQueueManager != null && mqQueueManager.isConnected()) {
try {
jmsContext.close();
mqQueueManager.disconnect();
LOG.info("Closed connection to IBM MQ: {}", queueManager);
} catch (Exception e) {
} catch (MQException e) {
LOG.error("Failed to close IBM MQ connection", e);
}
}
@ -477,7 +594,7 @@ public class IbmMqConnector implements Connector {
throw new IllegalStateException("Keystore not found: " + keystorePath);
}
keyStore.load(ksStream, keystorePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keystorePassword.toCharArray());
@ -499,4 +616,34 @@ public class IbmMqConnector implements Connector {
return sslContext.getSocketFactory();
}
private record Selector(Map<String, String> expectedValues) {
private static Selector parse(String selector) {
if (selector == null || selector.isBlank()) {
return new Selector(Map.of());
}
Map<String, String> predicates = new HashMap<>();
for (String predicate : selector.split("(?i)\\s+AND\\s+")) {
Matcher matcher = SELECTOR_EQUALS_PATTERN.matcher(predicate);
if (!matcher.matches()) {
throw new IllegalArgumentException(
"Unsupported IBM MQ selector expression. Supported format: property = 'value' joined by AND. Selector: "
+ selector);
}
predicates.put(matcher.group(1), matcher.group(2).replace("''", "'"));
}
return new Selector(predicates);
}
private boolean matches(Map<String, String> headers) {
for (Map.Entry<String, String> expectedValue : expectedValues.entrySet()) {
if (!expectedValue.getValue().equals(headers.get(expectedValue.getKey()))) {
return false;
}
}
return true;
}
}
}

View File

@ -1,348 +1,369 @@
package cz.moneta.test.harness.connectors.messaging;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import org.apache.avro.generic.GenericRecord;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
import cz.moneta.test.harness.messaging.exception.MessagingDestinationException;
import cz.moneta.test.harness.messaging.exception.MessagingSchemaException;
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
import cz.moneta.test.harness.support.messaging.kafka.MessageContentType;
import cz.moneta.test.harness.support.messaging.kafka.ReceivedMessage;
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
import io.confluent.kafka.serializers.KafkaAvroSerializer;
/**
* Kafka connector for sending and receiving messages.
* Supports Avro serialization with Confluent Schema Registry.
* <p>
* Uses manual partition assignment (no consumer group) for test isolation.
* Each receive operation creates a new consumer to avoid offset sharing.
*/
public class KafkaConnector implements cz.moneta.test.harness.connectors.Connector {
private static final Logger LOG = LoggerFactory.getLogger(KafkaConnector.class);
private final Properties producerConfig;
private final Properties consumerConfig;
private final String schemaRegistryUrl;
private final CachedSchemaRegistryClient schemaRegistryClient;
private KafkaProducer<String, GenericRecord> producer;
/**
* Creates a new KafkaConnector.
*
* @param bootstrapServers Kafka bootstrap servers
* @param apiKey Kafka API key
* @param apiSecret Kafka API secret
* @param schemaRegistryUrl Schema Registry URL
* @param schemaRegistryApiKey Schema Registry API key
* @param schemaRegistryApiSecret Schema Registry API secret
*/
public KafkaConnector(String bootstrapServers,
String apiKey,
String apiSecret,
String schemaRegistryUrl,
String schemaRegistryApiKey,
String schemaRegistryApiSecret) {
this.schemaRegistryUrl = schemaRegistryUrl;
this.schemaRegistryClient = new CachedSchemaRegistryClient(
Collections.singletonList(schemaRegistryUrl), 100, new HashMap<>());
this.producerConfig = createProducerConfig(bootstrapServers, apiKey, apiSecret);
this.consumerConfig = createConsumerConfig(bootstrapServers, schemaRegistryApiKey, schemaRegistryApiSecret);
}
/**
* Creates producer configuration.
*/
private Properties createProducerConfig(String bootstrapServers, String apiKey, String apiSecret) {
Properties config = new Properties();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
config.put("schema.registry.url", schemaRegistryUrl);
config.put(ProducerConfig.ACKS_CONFIG, "all");
config.put(ProducerConfig.LINGER_MS_CONFIG, 1);
config.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// SASL/PLAIN authentication
// config.put("security.protocol", "SASL_SSL");
// config.put("sasl.mechanism", "PLAIN");
// config.put("sasl.jaas.config",
// "org.apache.kafka.common.security.plain.PlainLoginModule required " +
// "username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
// SSL configuration
// config.put("ssl.endpoint.identification.algorithm", "https");
return config;
}
/**
* Creates consumer configuration.
*/
private Properties createConsumerConfig(String bootstrapServers, String apiKey, String apiSecret) {
Properties config = new Properties();
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class);
config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "none");
config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
// SASL/PLAIN authentication
config.put("security.protocol", "SASL_SSL");
config.put("sasl.mechanism", "PLAIN");
config.put("sasl.jaas.config",
"org.apache.kafka.common.security.plain.PlainLoginModule required " +
"username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
// Schema Registry for deserialization
config.put("schema.registry.url", schemaRegistryUrl);
config.put("specific.avro.reader", false);
// SSL configuration
config.put("ssl.endpoint.identification.algorithm", "https");
return config;
}
/**
* Sends a message to a Kafka topic.
*/
public void send(String topic, String key, String jsonPayload, Map<String, String> headers) {
try {
org.apache.avro.Schema schema = getSchemaForTopic(topic);
GenericRecord record = jsonToAvro(jsonPayload, schema);
ProducerRecord<String, GenericRecord> producerRecord =
new ProducerRecord<>(topic, key, record);
// Add headers
if (headers != null) {
Headers kafkaHeaders = producerRecord.headers();
headers.forEach((k, v) ->
kafkaHeaders.add(k, v.getBytes(StandardCharsets.UTF_8)));
}
// Send and wait for confirmation
getProducer().send(producerRecord, (metadata, exception) -> {
if (exception != null) {
LOG.error("Failed to send message", exception);
} else {
LOG.debug("Message sent to topic {} partition {} offset {}",
metadata.topic(), metadata.partition(), metadata.offset());
}
}).get(10, java.util.concurrent.TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw new MessagingConnectionException(
"Failed to send message to topic " + topic, e.getCause());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new MessagingConnectionException(
"Interrupted while sending message to topic " + topic, e);
} catch (MessagingSchemaException e) {
throw e;
} catch (Exception e) {
throw new MessagingSchemaException(
"Failed to convert JSON to Avro for topic " + topic, e);
}
}
/**
* Receives a message from a Kafka topic matching the filter.
*/
public List<ReceivedMessage> receive(String topic,
Predicate<ReceivedMessage> filter,
Duration timeout) {
KafkaConsumer<String, GenericRecord> consumer = null;
try {
consumer = new KafkaConsumer<>(consumerConfig);
// Get partitions for the topic
List<TopicPartition> partitions = getPartitionsForTopic(consumer, topic);
if (partitions.isEmpty()) {
throw new MessagingDestinationException(
"Topic '" + topic + "' does not exist or has no partitions");
}
// Assign partitions and seek to end
consumer.assign(partitions);
// consumer.seekToBeginning(partitions);
consumer.seekToBeginning(partitions);
// Poll loop with exponential backoff
long startTime = System.currentTimeMillis();
Duration pollInterval = Duration.ofMillis(100);
Duration maxPollInterval = Duration.ofSeconds(1);
while (Duration.ofMillis(System.currentTimeMillis() - startTime).compareTo(timeout) < 0) {
ConsumerRecords<String, GenericRecord> records = consumer.poll(pollInterval);
for (ConsumerRecord<String, GenericRecord> record : records) {
ReceivedMessage message = convertToReceivedMessage(record, topic);
if (filter.test(message)) {
LOG.debug("Found matching message on topic {} partition {} offset {}",
record.topic(), record.partition(), record.offset());
return Collections.singletonList(message);
}
}
// Exponential backoff
pollInterval = Duration.ofMillis(
Math.min(pollInterval.toMillis() * 2, maxPollInterval.toMillis()));
}
throw new MessagingTimeoutException(
"No message matching filter found on topic '" + topic + "' within " + timeout);
} finally {
if (consumer != null) {
consumer.close();
}
}
}
/**
* Gets partitions for a topic.
*/
private List<TopicPartition> getPartitionsForTopic(KafkaConsumer<?, ?> consumer, String topic) {
List<TopicPartition> partitions = new ArrayList<>();
List<org.apache.kafka.common.PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
if (partitionInfos != null) {
for (org.apache.kafka.common.PartitionInfo partitionInfo : partitionInfos) {
partitions.add(new TopicPartition(topic, partitionInfo.partition()));
}
}
return partitions;
}
/**
* Saves current offsets for a topic.
*/
public Map<TopicPartition, Long> saveOffsets(String topic) {
return new HashMap<>();
}
/**
* Closes the connector and releases resources.
*/
@Override
public void close() {
if (producer != null) {
producer.close();
}
}
/**
* Gets or creates the producer (singleton, thread-safe).
*/
private KafkaProducer<String, GenericRecord> getProducer() {
if (producer == null) {
synchronized (this) {
if (producer == null) {
producer = new KafkaProducer<>(producerConfig);
}
}
}
return producer;
}
/**
* Retrieves schema from Schema Registry based on topic name.
*/
private org.apache.avro.Schema getSchemaForTopic(String topic) {
String subject = topic + "-value";
try {
io.confluent.kafka.schemaregistry.client.SchemaMetadata metadata =
schemaRegistryClient.getLatestSchemaMetadata(subject);
return new org.apache.avro.Schema.Parser().parse(metadata.getSchema());
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("404")) {
throw new MessagingSchemaException(
"Schema not found for subject '" + subject + "' in Schema Registry. " +
"Make sure the topic exists and schema is registered.");
}
throw new MessagingSchemaException(
"Failed to retrieve schema for topic '" + topic + "'", e);
}
}
/**
* Converts JSON string to Avro GenericRecord.
*/
private GenericRecord jsonToAvro(String json, org.apache.avro.Schema schema) {
try {
GenericRecord genericRecord = JsonToAvroConverter.processJson(json, schema);
return genericRecord;
} catch (Exception e) {
throw new MessagingSchemaException("Failed to convert JSON to Avro: " + e.getMessage(), e);
}
}
/**
* Converts Kafka ConsumerRecord to ReceivedMessage.
*/
private ReceivedMessage convertToReceivedMessage(ConsumerRecord<String, GenericRecord> record, String topic) {
try {
String jsonBody = avroToJson(record.value());
Map<String, String> headers = new HashMap<>();
Headers kafkaHeaders = record.headers();
for (org.apache.kafka.common.header.Header header : kafkaHeaders) {
if (header.value() != null) {
headers.put(header.key(), new String(header.value(), StandardCharsets.UTF_8));
}
}
return ReceivedMessage.builder()
.body(jsonBody)
.contentType(MessageContentType.JSON)
.headers(headers)
.timestamp(record.timestamp())
.source(topic)
.key(record.key())
.build();
} catch (Exception e) {
LOG.error("Failed to convert Avro record to ReceivedMessage", e);
throw new RuntimeException("Failed to convert message", e);
}
}
/**
* Converts Avro GenericRecord to JSON string.
*/
private String avroToJson(GenericRecord record) {
try {
return record.toString();
} catch (Exception e) {
throw new RuntimeException("Failed to convert Avro to JSON: " + e.getMessage(), e);
}
}
}
package cz.moneta.test.harness.connectors.messaging;
import cz.moneta.test.harness.connectors.messaging.kafkautils.CustomKafkaAvroDeserializer;
import cz.moneta.test.harness.connectors.messaging.kafkautils.JsonToAvroConverter;
import cz.moneta.test.harness.support.messaging.exception.MessagingConnectionException;
import cz.moneta.test.harness.support.messaging.exception.MessagingDestinationException;
import cz.moneta.test.harness.support.messaging.exception.MessagingSchemaException;
import cz.moneta.test.harness.support.messaging.exception.MessagingTimeoutException;
import cz.moneta.test.harness.support.messaging.kafka.MessageContentType;
import cz.moneta.test.harness.support.messaging.kafka.ReceivedMessage;
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
import io.confluent.kafka.schemaregistry.client.SchemaRegistryClientConfig;
import io.confluent.kafka.serializers.AbstractKafkaSchemaSerDeConfig;
import io.confluent.kafka.serializers.KafkaAvroSerializer;
import org.apache.avro.generic.GenericRecord;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
/**
* Kafka connector for sending and receiving messages.
* Supports Avro serialization with Confluent Schema Registry.
* <p>
* Uses manual partition assignment (no consumer group) for test isolation.
* Each receive operation creates a new consumer to avoid offset sharing.
*/
public class KafkaConnector implements cz.moneta.test.harness.connectors.Connector {
private static final Logger LOG = LogManager.getLogger(KafkaConnector.class);
private final Properties producerConfig;
private final Properties consumerConfig;
private final String schemaRegistryUrl;
private final CachedSchemaRegistryClient schemaRegistryClient;
private KafkaProducer<String, GenericRecord> producer;
/**
* Creates a new KafkaConnector.
*
* @param bootstrapServers Kafka bootstrap servers
* @param apiKey Kafka API key
* @param apiSecret Kafka API secret
* @param schemaRegistryUrl Schema Registry URL
* @param schemaRegistryApiKey Schema Registry API key
* @param schemaRegistryApiSecret Schema Registry API secret
*/
public KafkaConnector(String bootstrapServers,
String apiKey,
String apiSecret,
String schemaRegistryUrl,
String schemaRegistryApiKey,
String schemaRegistryApiSecret) {
this.schemaRegistryUrl = schemaRegistryUrl;
HashMap<String, String> schemaRegistryProps = new HashMap<>();
schemaRegistryProps.put(SchemaRegistryClientConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
schemaRegistryProps.put(SchemaRegistryClientConfig.USER_INFO_CONFIG, schemaRegistryApiKey + ":" + schemaRegistryApiSecret);
this.schemaRegistryClient = new CachedSchemaRegistryClient(
Collections.singletonList(schemaRegistryUrl), 100, schemaRegistryProps);
this.producerConfig = createProducerConfig(bootstrapServers, apiKey, apiSecret, schemaRegistryApiKey, schemaRegistryApiSecret);
this.consumerConfig = createConsumerConfig(bootstrapServers, apiKey, apiSecret, schemaRegistryApiKey, schemaRegistryApiSecret);
}
/**
* Creates producer configuration.
*/
private Properties createProducerConfig(String bootstrapServers, String apiKey, String apiSecret,
String schemaRegistryApiKey, String schemaRegistryApiSecret) {
Properties config = new Properties();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
config.put(ProducerConfig.ACKS_CONFIG, "all");
config.put(ProducerConfig.LINGER_MS_CONFIG, 1);
config.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
config.put(AbstractKafkaSchemaSerDeConfig.AUTO_REGISTER_SCHEMAS, false);
config.put(AbstractKafkaSchemaSerDeConfig.USE_LATEST_VERSION, true);
config.put(AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
config.put(SchemaRegistryClientConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
config.put(SchemaRegistryClientConfig.USER_INFO_CONFIG, schemaRegistryApiKey + ":" + schemaRegistryApiSecret);
// SASL/PLAIN authentication
config.put("security.protocol", "SASL_SSL");
config.put("sasl.mechanism", "PLAIN");
config.put("sasl.jaas.config",
"org.apache.kafka.common.security.plain.PlainLoginModule required " +
"username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
// SSL configuration
config.put("ssl.endpoint.identification.algorithm", "https");
return config;
}
/**
* Creates consumer configuration.
*/
private Properties createConsumerConfig(String bootstrapServers, String apiKey, String apiSecret,
String schemaRegistryApiKey, String schemaRegistryApiSecret) {
Properties config = new Properties();
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, CustomKafkaAvroDeserializer.class);
config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "none");
config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
// SASL/PLAIN authentication
config.put("security.protocol", "SASL_SSL");
config.put("sasl.mechanism", "PLAIN");
config.put("sasl.jaas.config",
"org.apache.kafka.common.security.plain.PlainLoginModule required " +
"username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
// Schema Registry for deserialization
config.put(AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
config.put(AbstractKafkaSchemaSerDeConfig.AUTO_REGISTER_SCHEMAS, false);
config.put(AbstractKafkaSchemaSerDeConfig.USE_LATEST_VERSION, true);
config.put(SchemaRegistryClientConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
config.put(SchemaRegistryClientConfig.USER_INFO_CONFIG, schemaRegistryApiKey + ":" + schemaRegistryApiSecret);
config.put("specific.avro.reader", false);
// SSL configuration
config.put("ssl.endpoint.identification.algorithm", "https");
return config;
}
/**
* Sends a message to a Kafka topic.
*/
public void send(String topic, String key, String jsonPayload, Map<String, String> headers) {
try {
org.apache.avro.Schema schema = getSchemaForTopic(topic);
GenericRecord record = jsonToAvro(jsonPayload, schema);
ProducerRecord<String, GenericRecord> producerRecord =
new ProducerRecord<>(topic, key, record);
// Add headers
if (headers != null) {
Headers kafkaHeaders = producerRecord.headers();
headers.forEach((k, v) ->
kafkaHeaders.add(k, v.getBytes(StandardCharsets.UTF_8)));
}
// Send and wait for confirmation
getProducer().send(producerRecord, (metadata, exception) -> {
if (exception != null) {
LOG.error("Failed to send message", exception);
} else {
LOG.debug("Message sent to topic {} partition {} offset {}",
metadata.topic(), metadata.partition(), metadata.offset());
}
}).get(10, java.util.concurrent.TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw new MessagingConnectionException(
"Failed to send message to topic " + topic, e.getCause());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new MessagingConnectionException(
"Interrupted while sending message to topic " + topic, e);
} catch (MessagingSchemaException e) {
throw e;
} catch (Exception e) {
throw new MessagingSchemaException(
"Failed to convert JSON to Avro for topic " + topic, e);
}
}
/**
* Receives a message from a Kafka topic matching the filter.
*/
public List<ReceivedMessage> receive(String topic,
Predicate<ReceivedMessage> filter,
Duration timeout, boolean startFromBeginning) {
KafkaConsumer<String, GenericRecord> consumer = null;
try {
consumer = new KafkaConsumer<>(consumerConfig);
// Get partitions for the topic
List<TopicPartition> partitions = getPartitionsForTopic(consumer, topic);
if (partitions.isEmpty()) {
throw new MessagingDestinationException(
"Topic '" + topic + "' does not exist or has no partitions");
}
consumer.assign(partitions);
if (startFromBeginning) {
consumer.seekToBeginning(partitions);
} else {
consumer.seekToEnd(partitions);
}
// Poll loop with exponential backoff
long startTime = System.currentTimeMillis();
Duration pollInterval = Duration.ofMillis(100);
Duration maxPollInterval = Duration.ofSeconds(1);
while (Duration.ofMillis(System.currentTimeMillis() - startTime).compareTo(timeout) < 0) {
ConsumerRecords<String, GenericRecord> records = consumer.poll(pollInterval);
for (ConsumerRecord<String, GenericRecord> record : records) {
ReceivedMessage message = convertToReceivedMessage(record, topic);
if (filter.test(message)) {
LOG.debug("Found matching message on topic {} partition {} offset {}",
record.topic(), record.partition(), record.offset());
return Collections.singletonList(message);
}
}
// Exponential backoff
pollInterval = Duration.ofMillis(
Math.min(pollInterval.toMillis() * 2, maxPollInterval.toMillis()));
}
throw new MessagingTimeoutException(
"No message matching filter found on topic '" + topic + "' within " + timeout);
} finally {
if (consumer != null) {
consumer.close();
}
}
}
/**
* Gets partitions for a topic.
*/
private List<TopicPartition> getPartitionsForTopic(KafkaConsumer<?, ?> consumer, String topic) {
List<TopicPartition> partitions = new ArrayList<>();
List<org.apache.kafka.common.PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
if (partitionInfos != null) {
for (org.apache.kafka.common.PartitionInfo partitionInfo : partitionInfos) {
partitions.add(new TopicPartition(topic, partitionInfo.partition()));
}
}
return partitions;
}
/**
* Saves current offsets for a topic.
*/
public Map<TopicPartition, Long> saveOffsets(String topic) {
return new HashMap<>();
}
/**
* Closes the connector and releases resources.
*/
@Override
public void close() {
if (producer != null) {
producer.close();
}
}
/**
* Gets or creates the producer (singleton, thread-safe).
*/
private KafkaProducer<String, GenericRecord> getProducer() {
if (producer == null) {
synchronized (this) {
if (producer == null) {
producer = new KafkaProducer<>(producerConfig);
}
}
}
return producer;
}
/**
* Retrieves schema from Schema Registry based on topic name.
*/
private org.apache.avro.Schema getSchemaForTopic(String topic) {
String subject = topic + "-value";
try {
io.confluent.kafka.schemaregistry.client.SchemaMetadata metadata =
schemaRegistryClient.getLatestSchemaMetadata(subject);
return new org.apache.avro.Schema.Parser().parse(metadata.getSchema());
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("404")) {
throw new MessagingSchemaException(
"Schema not found for subject '" + subject + "' in Schema Registry. " +
"Make sure the topic exists and schema is registered.");
}
throw new MessagingSchemaException(
"Failed to retrieve schema for topic '" + topic + "'", e);
}
}
/**
* Converts JSON string to Avro GenericRecord.
*/
private GenericRecord jsonToAvro(String json, org.apache.avro.Schema schema) {
try {
GenericRecord genericRecord = JsonToAvroConverter.processJson(json, schema);
return genericRecord;
} catch (Exception e) {
throw new MessagingSchemaException("Failed to convert JSON to Avro: " + e.getMessage(), e);
}
}
/**
* Converts Kafka ConsumerRecord to ReceivedMessage.
*/
private ReceivedMessage convertToReceivedMessage(ConsumerRecord<String, GenericRecord> record, String topic) {
try {
String jsonBody;
if (null != record.value()) {
jsonBody = avroToJson(record.value());
} else {
jsonBody = "";
}
Map<String, String> headers = new HashMap<>();
Headers kafkaHeaders = record.headers();
for (org.apache.kafka.common.header.Header header : kafkaHeaders) {
if (header.value() != null) {
headers.put(header.key(), new String(header.value(), StandardCharsets.UTF_8));
}
}
return ReceivedMessage.builder()
.body(jsonBody)
.contentType(MessageContentType.JSON)
.headers(headers)
.timestamp(record.timestamp())
.source(topic)
.key(record.key())
.build();
} catch (Exception e) {
LOG.error("Failed to convert Avro record to ReceivedMessage", e);
throw new RuntimeException("Failed to convert message", e);
}
}
/**
* Converts Avro GenericRecord to JSON string.
*/
private String avroToJson(GenericRecord record) {
try {
return record.toString();
} catch (Exception e) {
throw new RuntimeException("Failed to convert Avro to JSON: " + e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,54 @@
package cz.moneta.test.harness.connectors.messaging.kafkautils;
import cz.moneta.test.harness.connectors.messaging.KafkaConnector;
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
import org.apache.avro.Schema;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.header.Headers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class CustomKafkaAvroDeserializer extends KafkaAvroDeserializer {
private static final Logger LOG = LogManager.getLogger(KafkaConnector.class);
@Override
public Object deserialize(String topic, byte[] bytes) {
try {
return super.deserialize(topic, bytes);
} catch (SerializationException e) {
LOG.error("Message deserialization by avro schema failed", e);
return null;
}
}
@Override
public Object deserialize(String topic, Headers headers, byte[] bytes) {
try {
return super.deserialize(topic, headers, bytes);
} catch (SerializationException e) {
LOG.error("Message deserialization by avro schema failed", e);
return null;
}
}
@Override
public Object deserialize(String topic, byte[] bytes, Schema readerSchema) {
try {
return super.deserialize(topic, bytes, readerSchema);
} catch (SerializationException e) {
LOG.error("Message deserialization by avro schema failed", e);
return null;
}
}
@Override
public Object deserialize(String topic, Headers headers, byte[] bytes, Schema readerSchema) {
try {
return super.deserialize(topic, headers, bytes, readerSchema);
} catch (SerializationException e) {
LOG.error("Message deserialization by avro schema failed", e);
return null;
}
}
}

View File

@ -1,115 +1,109 @@
package cz.moneta.test.harness.connectors.messaging;
import com.google.gson.*;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.JsonDecoder;
import java.util.ArrayList;
import java.util.List;
public class JsonToAvroConverter {
protected static GenericRecord processJson(String json, Schema schema) throws IllegalArgumentException, JsonSchemaException {
GenericRecord result = (GenericRecord) jsonElementToAvro(JsonParser.parseString(json), schema);
return result;
}
private static Object jsonElementToAvro(JsonElement element, Schema schema) throws JsonSchemaException {
boolean schemaIsNullable = isNullable(schema);
if (schemaIsNullable) {
schema = typeFromNullable(schema);
}
if (element == null || element.isJsonNull()) {
if (!schemaIsNullable) {
throw new JsonSchemaException("The element is not nullable in Avro schema.");
}
return null;
} else if (element.isJsonObject()) {
if (schema.getType() != Schema.Type.RECORD) {
throw new JsonSchemaException(
String.format("The element `%s` doesn't match Avro type RECORD", element));
}
return jsonObjectToAvro(element.getAsJsonObject(), schema);
} else if (element.isJsonArray()) {
if (schema.getType() != Schema.Type.ARRAY) {
throw new JsonSchemaException(
String.format("The element `%s` doesn't match Avro type ARRAY", element));
}
JsonArray jsonArray = element.getAsJsonArray();
List<Object> avroArray = new ArrayList<>(jsonArray.size());
for (JsonElement e : element.getAsJsonArray()) {
avroArray.add(jsonElementToAvro(e, schema.getElementType()));
}
return avroArray;
} else if (element.isJsonPrimitive()) {
return jsonPrimitiveToAvro(element.getAsJsonPrimitive(), schema);
} else {
throw new JsonSchemaException(
String.format(
"The Json element `%s` is of an unknown class %s", element, element.getClass()));
}
}
private static GenericRecord jsonObjectToAvro(JsonObject jsonObject, Schema schema) throws JsonSchemaException {
GenericRecord avroRecord = new GenericData.Record(schema);
for (Schema.Field field : schema.getFields()) {
avroRecord.put(field.name(), jsonElementToAvro(jsonObject.get(field.name()), field.schema()));
}
return avroRecord;
}
private static boolean isNullable(Schema type) {
return type.getType() == Schema.Type.NULL
|| type.getType() == Schema.Type.UNION
&& type.getTypes().stream().anyMatch(JsonToAvroConverter::isNullable);
}
private static Schema typeFromNullable(Schema type) {
if (type.getType() == Schema.Type.UNION) {
return typeFromNullable(
type.getTypes().stream()
.filter(t -> t.getType() != Schema.Type.NULL)
.findFirst()
.orElseThrow(
() ->
new IllegalStateException(
String.format("Type `%s` should have a non null subtype", type))));
}
return type;
}
private static Object jsonPrimitiveToAvro(JsonPrimitive primitive, Schema schema){
switch (schema.getType()) {
case NULL:
return null;
case STRING:
return primitive.getAsString();
case BOOLEAN:
return primitive.getAsBoolean();
case INT:
return primitive.getAsInt();
case LONG:
return primitive.getAsLong();
case FLOAT:
return primitive.getAsFloat();
case DOUBLE:
return primitive.getAsDouble();
default:
return primitive.getAsString();
}
}
private static class JsonSchemaException extends Exception {
JsonSchemaException(String message) {
super(message);
}
}
}
package cz.moneta.test.harness.connectors.messaging.kafkautils;
import com.google.gson.*;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericRecord;
import java.util.ArrayList;
import java.util.List;
public class JsonToAvroConverter {
public static GenericRecord processJson(String json, Schema schema) throws IllegalArgumentException, JsonSchemaException {
GenericRecord result = (GenericRecord) jsonElementToAvro(JsonParser.parseString(json), schema);
return result;
}
private static Object jsonElementToAvro(JsonElement element, Schema schema) throws JsonSchemaException {
boolean schemaIsNullable = isNullable(schema);
if (schemaIsNullable) {
schema = typeFromNullable(schema);
}
if (element == null || element.isJsonNull()) {
if (!schemaIsNullable) {
throw new JsonSchemaException("The element is not nullable in Avro schema.");
}
return null;
} else if (element.isJsonObject()) {
if (schema.getType() != Schema.Type.RECORD) {
throw new JsonSchemaException(
String.format("The element `%s` doesn't match Avro type RECORD", element));
}
return jsonObjectToAvro(element.getAsJsonObject(), schema);
} else if (element.isJsonArray()) {
if (schema.getType() != Schema.Type.ARRAY) {
throw new JsonSchemaException(
String.format("The element `%s` doesn't match Avro type ARRAY", element));
}
JsonArray jsonArray = element.getAsJsonArray();
List<Object> avroArray = new ArrayList<>(jsonArray.size());
for (JsonElement e : element.getAsJsonArray()) {
avroArray.add(jsonElementToAvro(e, schema.getElementType()));
}
return avroArray;
} else if (element.isJsonPrimitive()) {
return jsonPrimitiveToAvro(element.getAsJsonPrimitive(), schema);
} else {
throw new JsonSchemaException(
String.format(
"The Json element `%s` is of an unknown class %s", element, element.getClass()));
}
}
private static GenericRecord jsonObjectToAvro(JsonObject jsonObject, Schema schema) throws JsonSchemaException {
GenericRecord avroRecord = new GenericData.Record(schema);
for (Schema.Field field : schema.getFields()) {
avroRecord.put(field.name(), jsonElementToAvro(jsonObject.get(field.name()), field.schema()));
}
return avroRecord;
}
private static boolean isNullable(Schema type) {
return type.getType() == Schema.Type.NULL
|| type.getType() == Schema.Type.UNION
&& type.getTypes().stream().anyMatch(JsonToAvroConverter::isNullable);
}
private static Schema typeFromNullable(Schema type) {
if (type.getType() == Schema.Type.UNION) {
return typeFromNullable(
type.getTypes().stream()
.filter(t -> t.getType() != Schema.Type.NULL)
.findFirst()
.orElseThrow(
() ->
new IllegalStateException(
String.format("Type `%s` should have a non null subtype", type))));
}
return type;
}
private static Object jsonPrimitiveToAvro(JsonPrimitive primitive, Schema schema) {
switch (schema.getType()) {
case NULL:
return null;
case STRING:
return primitive.getAsString();
case BOOLEAN:
return primitive.getAsBoolean();
case INT:
return primitive.getAsInt();
case LONG:
return primitive.getAsLong();
case FLOAT:
return primitive.getAsFloat();
case DOUBLE:
return primitive.getAsDouble();
default:
return primitive.getAsString();
}
}
private static class JsonSchemaException extends Exception {
JsonSchemaException(String message) {
super(message);
}
}
}

View File

@ -1,338 +1,338 @@
package cz.moneta.test.harness.connectors.mobile;
import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup;
import cz.moneta.test.harness.support.web.TextContainer;
import cz.moneta.test.harness.support.web.Until;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileBy;
import io.appium.java_client.remote.MobileCapabilityType;
import io.appium.java_client.touch.offset.PointOption;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.*;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import static io.appium.java_client.touch.offset.PointOption.point;
import static java.time.Duration.ofMillis;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent;
public abstract class AppiumMobileConnector<A extends AppiumDriver> implements Connector {
private static final Logger logger = LogManager.getLogger(AppiumMobileConnector.class);
private static final int LAZY_ELEMENT_RENDER_TIMEOUT = 5;
protected String deviceName;
protected String udid;
protected String bundleId;
protected String appBinaryPath;
protected String host;
protected final StoreAccessor store;
private final Function<String, By> defaultLookup;
protected abstract A getDriver();
public abstract void captureVideo(String snapshotFileName);
public abstract void resetApp();
public abstract void close();
public AppiumMobileConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String appBinaryPath) {
this.store = store;
this.host = host;
this.defaultLookup = defaultLookup;
this.appBinaryPath = appBinaryPath;
}
public String getDeviceName() {
return deviceName;
}
public StoreAccessor getStore() {
return store;
}
protected void setDesiredCapabilities(Platform platformName, String deviceName, String udid, String automationName, DesiredCapabilities capabilities) {
capabilities.setCapability(CapabilityType.PLATFORM_NAME, platformName);
// capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName);
// capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 150);
// capabilities.setCapability(MobileCapabilityType.NO_RESET, true);
// capabilities.setCapability(MobileCapabilityType.UDID, udid);
// capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, automationName);
// capabilities.setCapability(MobileCapabilityType.APP, appBinaryPath);
}
protected Optional<A> launchExistingApp(A driver) {
// return Optional.of(driver)
// .map(app -> {
// driver.launchApp();
// return driver;
// });
return Optional.of(driver);
// TODO: actually not supported
}
private void installApp(String path, A driver) {
// Optional.of(bundleId)
// .filter(app -> !driver.isAppInstalled(app))
// .map(installApp -> {
// logger.info("App was not found on the device " + deviceName + " and will be installed");
// try {
// driver.installApp(path);
// } catch (WebDriverException e) {
// throw new HarnessException("The application at " + path + " does not exist or is not accessible. " +
// "Please, check if the path is correct");
// }
// return driver;
// });
// TODO: actually not supported
}
protected void mobileLanguage(DesiredCapabilities capabilities, String ENPlatformLocale, String CZPlatformLocale) {
String languageKey = store.getConfig("appium.language", "cs");
switch (languageKey) {
case "en":
capabilities.setCapability(MobileCapabilityType.LANGUAGE, "en");
capabilities.setCapability(MobileCapabilityType.LOCALE, ENPlatformLocale);
break;
case "cs":
capabilities.setCapability(MobileCapabilityType.LANGUAGE, "cs");
capabilities.setCapability(MobileCapabilityType.LOCALE, CZPlatformLocale);
break;
default:
throw new HarnessException("Please, define the appium.language parameter. Available language is cs or en");
}
}
protected Boolean checkVideoRecord() {
return Boolean.parseBoolean(store.getConfig("appium.video.record", "false"));
}
public void click(String path, MobileLookup mobileLookup) {
// new TouchAction<>(getDriver())
// .tap(tapOptions().withElement(element(waitForLazyElement(path, mobileLookup))))
// .perform();
// TODO: actually not supported
}
protected WebElement waitForLazyElement(String path, MobileLookup mobileLookup) {
doWaitForLazyElement(path, mobileLookup);
return getDriver().findElement(resolveMobileLookup(mobileLookup, path));
}
List<WebElement> waitForLazyElements(String path, MobileLookup mobileLookup) {
doWaitForLazyElement(path, mobileLookup);
return getDriver().findElements(resolveMobileLookup(mobileLookup, path));
}
private void doWaitForLazyElement(String path, MobileLookup mobileLookup) {
new FluentWait<>(getDriver())
.withTimeout(Duration.ofSeconds(LAZY_ELEMENT_RENDER_TIMEOUT))
.pollingEvery(ofMillis(300))
.ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class)
.withMessage("Element/s not found within " + LAZY_ELEMENT_RENDER_TIMEOUT
+ " seconds timeout. If this error occurs, you might want to consider using explicit @AndroidWait or @IosWait annotation. " + path)
.until(driver -> !driver.findElements(resolveMobileLookup(mobileLookup, path)).isEmpty());
}
private Alert waitForAlert(int timeout) {
return new FluentWait<>(getDriver())
.withTimeout(Duration.ofSeconds(timeout))
.pollingEvery(Duration.ofMillis(300))
.withMessage("Alert is not present after " + timeout + " seconds timeout.")
.until(driver -> alertIsPresent().apply(getDriver()));
}
public void acceptAlert(int timeout) {
waitForAlert(timeout).accept();
}
public void dismissAlert(int timeout) {
waitForAlert(timeout).dismiss();
}
public String getText(String path, MobileLookup mobileLookup) {
return waitForLazyElement(path, mobileLookup).getText();
}
public void waitForElements(int timeoutSeconds, MobileLookup mobileLookup, Until until, String... elementsToCheck) {
WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeoutSeconds));
Arrays.stream(elementsToCheck)
.forEach(path -> wait.until(resolveUntil(until).apply(resolveMobileLookup(mobileLookup, path))));
}
public void scrollRight() {
horizontalScroll(0.2, 0.8, 0.5);
}
public void scrollLeft() {
horizontalScroll(0.8, 0.2, 0.5);
}
public void swipeViewLeft() {
horizontalScroll(0.9, 0.02, 0.88);
}
public void swipeViewRight() {
horizontalScroll(0.02, 0.9, 0.88);
}
private void horizontalScroll(double startPercentage, double endPercentage, double anchorPercentage) {
Dimension size = getDriver().manage().window().getSize();
int anchor = (int) (size.height * anchorPercentage);
int startPoint = (int) (size.width * startPercentage);
int endPoint = (int) (size.width * endPercentage);
doScroll(point(startPoint, anchor), point(endPoint, anchor));
}
public void scrollDown() {
verticalScroll(0.5, 0.2);
}
public void scrollUp() {
verticalScroll(0.2, 0.5);
}
public void scrollLeftUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollLeft);
}
public void scrollRightUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollRight);
}
public void scrollDownUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollDown);
}
public void scrollUpUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollUp);
}
public void swipeFromToElement(String source, String target, MobileLookup mobileLookup) {
// Point fromElementPosition = waitForLazyElement(source, mobileLookup).getCenter();
// Point toElementPosition = getDriver().findElement(resolveMobileLookup(mobileLookup, target)).getCenter();
// doScroll(point(fromElementPosition.x, fromElementPosition.y), point(toElementPosition.x, toElementPosition.y));
// TODO: actually not supported
}
private void doScrollUntil(String path, MobileLookup mobileLookup, Runnable scroll) {
Stream.generate(() -> scroll)
.limit(10)
.peek(Runnable::run)
.filter(r -> isNotEmpty(getDriver().findElements(resolveMobileLookup(mobileLookup, path))))
.findFirst()
.orElseThrow(() ->
new HarnessException(String.format("Element '%s' not present after 10 swipes", path)));
}
private void verticalScroll(double startPercentage, double endPercentage) {
Dimension size = getDriver().manage().window().getSize();
int anchor = (int) (size.width * 0.3);
int startPoint = (int) (size.height * startPercentage);
int endPoint = (int) (size.height * endPercentage);
doScroll(point(anchor, startPoint), point(anchor, endPoint));
}
private void doScroll(PointOption from, PointOption to) {
// new TouchAction(getDriver())
// .press(from)
// .waitAction(waitOptions(ofMillis(1000)))
// .moveTo(to)
// .release().perform();
// TODO: acctualy not supported
}
public void takeSnapshot(String snapshotFileName) {
String snapshotsPath = store.getConfig("appium.snapshots.path", "target/screenshots");
File file = Paths.get(snapshotsPath, snapshotFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, getDriver().getScreenshotAs(OutputType.BYTES));
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + snapshotFileName + " to " + snapshotsPath, e);
}
}
public void saveSource(String sourceFileName) {
String sourcesPath = store.getConfig("appium.view.sources.path", "target/sources");
File file = Paths.get(sourcesPath, sourceFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, getDriver().getPageSource().getBytes());
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + sourceFileName + " to " + sourcesPath, e);
}
}
public void type(TextContainer input, String text, boolean clear, MobileLookup mobileLookup) {
if (clear) {
clearInput(input, mobileLookup);
}
waitForLazyElement(input.getPath(), mobileLookup).sendKeys(text);
}
private void clearInput(TextContainer input, MobileLookup mobileLookup) {
waitForLazyElement(input.getPath(), mobileLookup).clear();
}
public WebElementsCheck elementsCheck() {
return new WebElementsCheck(this);
}
private By resolveMobileLookup(MobileLookup mobileLookup, String path) {
if (MobilePlatform.isAndroid(getStore())) {
path = mobileLookup.equals(MobileLookup.ANDROID_TEXT) ? "new UiSelector().textContains(\"" + path + "\")" : path;
path = mobileLookup.equals(MobileLookup.ID) ? bundleId + ":id/" + path : path;
}
return resolveLookup(mobileLookup).apply(path);
}
private Function<String, By> resolveLookup(MobileLookup mobileLookup) {
switch (mobileLookup) {
case XPATH:
return MobileBy::xpath;
case ID:
return MobileBy::id;
case IOS_NAME:
return MobileBy::name;
case ANDROID_TEXT:
return MobileBy::AndroidUIAutomator;
default:
return defaultLookup;
}
}
private Function<By, ExpectedCondition<?>> resolveUntil(Until until) {
switch (until) {
case VISIBLE:
return ExpectedConditions::visibilityOfElementLocated;
case PRESENT_IN_DOM:
return ExpectedConditions::presenceOfElementLocated;
case GONE:
return ExpectedConditions::invisibilityOfElementLocated;
default:
return ExpectedConditions::presenceOfElementLocated;
}
}
}
package cz.moneta.test.harness.connectors.mobile;
import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup;
import cz.moneta.test.harness.support.web.TextContainer;
import cz.moneta.test.harness.support.web.Until;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileBy;
import io.appium.java_client.remote.MobileCapabilityType;
import io.appium.java_client.touch.offset.PointOption;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.*;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import static io.appium.java_client.touch.offset.PointOption.point;
import static java.time.Duration.ofMillis;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent;
public abstract class AppiumMobileConnector<A extends AppiumDriver> implements Connector {
private static final Logger logger = LogManager.getLogger(AppiumMobileConnector.class);
private static final int LAZY_ELEMENT_RENDER_TIMEOUT = 5;
protected String deviceName;
protected String udid;
protected String bundleId;
protected String appBinaryPath;
protected String host;
protected final StoreAccessor store;
private final Function<String, By> defaultLookup;
protected abstract A getDriver();
public abstract void captureVideo(String snapshotFileName);
public abstract void resetApp();
public abstract void close();
public AppiumMobileConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String appBinaryPath) {
this.store = store;
this.host = host;
this.defaultLookup = defaultLookup;
this.appBinaryPath = appBinaryPath;
}
public String getDeviceName() {
return deviceName;
}
public StoreAccessor getStore() {
return store;
}
protected void setDesiredCapabilities(Platform platformName, String deviceName, String udid, String automationName, DesiredCapabilities capabilities) {
capabilities.setCapability(CapabilityType.PLATFORM_NAME, platformName);
// capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName);
// capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 150);
// capabilities.setCapability(MobileCapabilityType.NO_RESET, true);
// capabilities.setCapability(MobileCapabilityType.UDID, udid);
// capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, automationName);
// capabilities.setCapability(MobileCapabilityType.APP, appBinaryPath);
}
protected Optional<A> launchExistingApp(A driver) {
// return Optional.of(driver)
// .map(app -> {
// driver.launchApp();
// return driver;
// });
return Optional.of(driver);
// TODO: actually not supported
}
private void installApp(String path, A driver) {
// Optional.of(bundleId)
// .filter(app -> !driver.isAppInstalled(app))
// .map(installApp -> {
// logger.info("App was not found on the device " + deviceName + " and will be installed");
// try {
// driver.installApp(path);
// } catch (WebDriverException e) {
// throw new HarnessException("The application at " + path + " does not exist or is not accessible. " +
// "Please, check if the path is correct");
// }
// return driver;
// });
// TODO: actually not supported
}
protected void mobileLanguage(DesiredCapabilities capabilities, String ENPlatformLocale, String CZPlatformLocale) {
String languageKey = store.getConfig("appium.language", "cs");
switch (languageKey) {
case "en":
capabilities.setCapability(MobileCapabilityType.LANGUAGE, "en");
capabilities.setCapability(MobileCapabilityType.LOCALE, ENPlatformLocale);
break;
case "cs":
capabilities.setCapability(MobileCapabilityType.LANGUAGE, "cs");
capabilities.setCapability(MobileCapabilityType.LOCALE, CZPlatformLocale);
break;
default:
throw new HarnessException("Please, define the appium.language parameter. Available language is cs or en");
}
}
protected Boolean checkVideoRecord() {
return Boolean.parseBoolean(store.getConfig("appium.video.record", "false"));
}
public void click(String path, MobileLookup mobileLookup) {
// new TouchAction<>(getDriver())
// .tap(tapOptions().withElement(element(waitForLazyElement(path, mobileLookup))))
// .perform();
// TODO: actually not supported
}
protected WebElement waitForLazyElement(String path, MobileLookup mobileLookup) {
doWaitForLazyElement(path, mobileLookup);
return getDriver().findElement(resolveMobileLookup(mobileLookup, path));
}
List<WebElement> waitForLazyElements(String path, MobileLookup mobileLookup) {
doWaitForLazyElement(path, mobileLookup);
return getDriver().findElements(resolveMobileLookup(mobileLookup, path));
}
private void doWaitForLazyElement(String path, MobileLookup mobileLookup) {
new FluentWait<>(getDriver())
.withTimeout(Duration.ofSeconds(LAZY_ELEMENT_RENDER_TIMEOUT))
.pollingEvery(ofMillis(300))
.ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class)
.withMessage("Element/s not found within " + LAZY_ELEMENT_RENDER_TIMEOUT
+ " seconds timeout. If this error occurs, you might want to consider using explicit @AndroidWait or @IosWait annotation. " + path)
.until(driver -> !driver.findElements(resolveMobileLookup(mobileLookup, path)).isEmpty());
}
private Alert waitForAlert(int timeout) {
return new FluentWait<>(getDriver())
.withTimeout(Duration.ofSeconds(timeout))
.pollingEvery(Duration.ofMillis(300))
.withMessage("Alert is not present after " + timeout + " seconds timeout.")
.until(driver -> alertIsPresent().apply(getDriver()));
}
public void acceptAlert(int timeout) {
waitForAlert(timeout).accept();
}
public void dismissAlert(int timeout) {
waitForAlert(timeout).dismiss();
}
public String getText(String path, MobileLookup mobileLookup) {
return waitForLazyElement(path, mobileLookup).getText();
}
public void waitForElements(int timeoutSeconds, MobileLookup mobileLookup, Until until, String... elementsToCheck) {
WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeoutSeconds));
Arrays.stream(elementsToCheck)
.forEach(path -> wait.until(resolveUntil(until).apply(resolveMobileLookup(mobileLookup, path))));
}
public void scrollRight() {
horizontalScroll(0.2, 0.8, 0.5);
}
public void scrollLeft() {
horizontalScroll(0.8, 0.2, 0.5);
}
public void swipeViewLeft() {
horizontalScroll(0.9, 0.02, 0.88);
}
public void swipeViewRight() {
horizontalScroll(0.02, 0.9, 0.88);
}
private void horizontalScroll(double startPercentage, double endPercentage, double anchorPercentage) {
Dimension size = getDriver().manage().window().getSize();
int anchor = (int) (size.height * anchorPercentage);
int startPoint = (int) (size.width * startPercentage);
int endPoint = (int) (size.width * endPercentage);
doScroll(point(startPoint, anchor), point(endPoint, anchor));
}
public void scrollDown() {
verticalScroll(0.5, 0.2);
}
public void scrollUp() {
verticalScroll(0.2, 0.5);
}
public void scrollLeftUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollLeft);
}
public void scrollRightUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollRight);
}
public void scrollDownUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollDown);
}
public void scrollUpUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollUp);
}
public void swipeFromToElement(String source, String target, MobileLookup mobileLookup) {
// Point fromElementPosition = waitForLazyElement(source, mobileLookup).getCenter();
// Point toElementPosition = getDriver().findElement(resolveMobileLookup(mobileLookup, target)).getCenter();
// doScroll(point(fromElementPosition.x, fromElementPosition.y), point(toElementPosition.x, toElementPosition.y));
// TODO: actually not supported
}
private void doScrollUntil(String path, MobileLookup mobileLookup, Runnable scroll) {
Stream.generate(() -> scroll)
.limit(10)
.peek(Runnable::run)
.filter(r -> isNotEmpty(getDriver().findElements(resolveMobileLookup(mobileLookup, path))))
.findFirst()
.orElseThrow(() ->
new HarnessException(String.format("Element '%s' not present after 10 swipes", path)));
}
private void verticalScroll(double startPercentage, double endPercentage) {
Dimension size = getDriver().manage().window().getSize();
int anchor = (int) (size.width * 0.3);
int startPoint = (int) (size.height * startPercentage);
int endPoint = (int) (size.height * endPercentage);
doScroll(point(anchor, startPoint), point(anchor, endPoint));
}
private void doScroll(PointOption from, PointOption to) {
// new TouchAction(getDriver())
// .press(from)
// .waitAction(waitOptions(ofMillis(1000)))
// .moveTo(to)
// .release().perform();
// TODO: acctualy not supported
}
public void takeSnapshot(String snapshotFileName) {
String snapshotsPath = store.getConfig("appium.snapshots.path", "target/screenshots");
File file = Paths.get(snapshotsPath, snapshotFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, getDriver().getScreenshotAs(OutputType.BYTES));
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + snapshotFileName + " to " + snapshotsPath, e);
}
}
public void saveSource(String sourceFileName) {
String sourcesPath = store.getConfig("appium.view.sources.path", "target/sources");
File file = Paths.get(sourcesPath, sourceFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, getDriver().getPageSource().getBytes());
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + sourceFileName + " to " + sourcesPath, e);
}
}
public void type(TextContainer input, String text, boolean clear, MobileLookup mobileLookup) {
if (clear) {
clearInput(input, mobileLookup);
}
waitForLazyElement(input.getPath(), mobileLookup).sendKeys(text);
}
private void clearInput(TextContainer input, MobileLookup mobileLookup) {
waitForLazyElement(input.getPath(), mobileLookup).clear();
}
public WebElementsCheck elementsCheck() {
return new WebElementsCheck(this);
}
private By resolveMobileLookup(MobileLookup mobileLookup, String path) {
if (MobilePlatform.isAndroid(getStore())) {
path = mobileLookup.equals(MobileLookup.ANDROID_TEXT) ? "new UiSelector().textContains(\"" + path + "\")" : path;
path = mobileLookup.equals(MobileLookup.ID) ? bundleId + ":id/" + path : path;
}
return resolveLookup(mobileLookup).apply(path);
}
private Function<String, By> resolveLookup(MobileLookup mobileLookup) {
switch (mobileLookup) {
case XPATH:
return MobileBy::xpath;
case ID:
return MobileBy::id;
case IOS_NAME:
return MobileBy::name;
case ANDROID_TEXT:
return MobileBy::AndroidUIAutomator;
default:
return defaultLookup;
}
}
private Function<By, ExpectedCondition<?>> resolveUntil(Until until) {
switch (until) {
case VISIBLE:
return ExpectedConditions::visibilityOfElementLocated;
case PRESENT_IN_DOM:
return ExpectedConditions::presenceOfElementLocated;
case GONE:
return ExpectedConditions::invisibilityOfElementLocated;
default:
return ExpectedConditions::presenceOfElementLocated;
}
}
}

View File

@ -1,15 +1,15 @@
package cz.moneta.test.harness.connectors.mobile;
import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform;
import org.openqa.selenium.Platform;
public class UnsupportedPlatformException extends AssertionError {
public UnsupportedPlatformException(MobilePlatform platform) {
super("Method is supported only for " + platform + ", please check your mobile platform or use another method");
}
public UnsupportedPlatformException(String message) {
super(message);
}
package cz.moneta.test.harness.connectors.mobile;
import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform;
import org.openqa.selenium.Platform;
public class UnsupportedPlatformException extends AssertionError {
public UnsupportedPlatformException(MobilePlatform platform) {
super("Method is supported only for " + platform + ", please check your mobile platform or use another method");
}
public UnsupportedPlatformException(String message) {
super(message);
}
}

View File

@ -1,28 +1,28 @@
package cz.moneta.test.harness.connectors.mobile;
import cz.moneta.test.harness.support.mobile.MobileLookup;
import org.openqa.selenium.WebElement;
import java.util.List;
public class WebElementsCheck {
private final AppiumMobileConnector connector;
public WebElementsCheck(AppiumMobileConnector connector) {
this.connector = connector;
}
public void checkElementContent(String path, String content, MobileLookup mobileLookup) {
List<WebElement> elements = connector.waitForLazyElements(path, mobileLookup);
elements.stream()
.map(WebElement::getText)
.filter(text -> text.contains(content))
.findFirst()
.orElseThrow(() -> new AssertionError(String.format("Cannot find element that contains text %s", content)));
}
public void checkElementPresent(String path, MobileLookup mobileLookup) {
connector.waitForLazyElement(path, mobileLookup);
}
}
package cz.moneta.test.harness.connectors.mobile;
import cz.moneta.test.harness.support.mobile.MobileLookup;
import org.openqa.selenium.WebElement;
import java.util.List;
public class WebElementsCheck {
private final AppiumMobileConnector connector;
public WebElementsCheck(AppiumMobileConnector connector) {
this.connector = connector;
}
public void checkElementContent(String path, String content, MobileLookup mobileLookup) {
List<WebElement> elements = connector.waitForLazyElements(path, mobileLookup);
elements.stream()
.map(WebElement::getText)
.filter(text -> text.contains(content))
.findFirst()
.orElseThrow(() -> new AssertionError(String.format("Cannot find element that contains text %s", content)));
}
public void checkElementPresent(String path, MobileLookup mobileLookup) {
connector.waitForLazyElement(path, mobileLookup);
}
}

View File

@ -1,123 +1,123 @@
package cz.moneta.test.harness.connectors.mobile.android;
import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.nativekey.AndroidKey;
import io.appium.java_client.android.nativekey.KeyEvent;
import io.appium.java_client.remote.AutomationName;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class AndroidConnector extends AppiumMobileConnector<AndroidDriver> {
private final AndroidDriver driver;
public AndroidConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String deviceName, String udid, String appPackage, String appBinaryPath) {
super(defaultLookup, store, host, appBinaryPath);
this.deviceName = deviceName;
this.udid = udid;
this.bundleId = appPackage;
this.driver = initDriver();
}
@Override
protected AndroidDriver getDriver() {
return driver;
}
private AndroidDriver initDriver() {
return startDriver()
.map(this::startRecording)
.flatMap(d ->
Stream.<Supplier<Optional<AndroidDriver>>>of(
() -> launchExistingApp(d))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst())
.orElseThrow(() -> new HarnessException("App was not started"));
}
private Optional<AndroidDriver> startDriver() {
return Optional.ofNullable(host)
.map(cs -> {
DesiredCapabilities capabilities = new DesiredCapabilities();
mobileLanguage(capabilities, "US", "CZ");
setDesiredCapabilities(Platform.ANDROID, deviceName, udid, getUiAutomator(), capabilities);
try {
return new AndroidDriver(new URL(cs), capabilities);
} catch (MalformedURLException e) {
throw new HarnessException(String.format("Appium connection to ANDROID device is invalid: %s", cs), e);
} catch (WebDriverException e) {
if (e.getCause() instanceof ConnectException) {
throw new HarnessException(String.format("Cannot connect to Appium on %s. Is your Appium Desktop running?", cs), e);
} else {
throw new HarnessException("Android driver didn't start, please check the connect to server", e);
}
}
});
}
@Override
public void captureVideo(String videoFileName) {
if (!checkVideoRecord()) {
return;
}
String videoPath = store.getConfig("appium.video.path", "target/videos");
File file = Paths.get(videoPath, videoFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, Base64.getDecoder().decode(driver.stopRecordingScreen()));
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + videoFileName + " to " + videoPath, e);
}
}
@Override
public void resetApp() {
driver.resetApp();
driver.activateApp(bundleId);
}
@Override
public void close() {
driver.quit();
}
public void hideAndroidKeyboard() {
driver.hideKeyboard();
}
private AndroidDriver startRecording(AndroidDriver driver) {
if (checkVideoRecord()) {
driver.startRecordingScreen();
}
return driver;
}
private String getUiAutomator() {
boolean uiAutomator = Boolean.parseBoolean(store.getConfig("appium.android.uiAutomator", "false"));
return uiAutomator ? AutomationName.ANDROID_UIAUTOMATOR2 : null;
}
public void pressAndroidKeys(AndroidKey... keys) {
Arrays.stream(keys)
.forEach(key -> getDriver().pressKey(new KeyEvent(key)));
}
}
package cz.moneta.test.harness.connectors.mobile.android;
import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.nativekey.AndroidKey;
import io.appium.java_client.android.nativekey.KeyEvent;
import io.appium.java_client.remote.AutomationName;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class AndroidConnector extends AppiumMobileConnector<AndroidDriver> {
private final AndroidDriver driver;
public AndroidConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String deviceName, String udid, String appPackage, String appBinaryPath) {
super(defaultLookup, store, host, appBinaryPath);
this.deviceName = deviceName;
this.udid = udid;
this.bundleId = appPackage;
this.driver = initDriver();
}
@Override
protected AndroidDriver getDriver() {
return driver;
}
private AndroidDriver initDriver() {
return startDriver()
.map(this::startRecording)
.flatMap(d ->
Stream.<Supplier<Optional<AndroidDriver>>>of(
() -> launchExistingApp(d))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst())
.orElseThrow(() -> new HarnessException("App was not started"));
}
private Optional<AndroidDriver> startDriver() {
return Optional.ofNullable(host)
.map(cs -> {
DesiredCapabilities capabilities = new DesiredCapabilities();
mobileLanguage(capabilities, "US", "CZ");
setDesiredCapabilities(Platform.ANDROID, deviceName, udid, getUiAutomator(), capabilities);
try {
return new AndroidDriver(new URL(cs), capabilities);
} catch (MalformedURLException e) {
throw new HarnessException(String.format("Appium connection to ANDROID device is invalid: %s", cs), e);
} catch (WebDriverException e) {
if (e.getCause() instanceof ConnectException) {
throw new HarnessException(String.format("Cannot connect to Appium on %s. Is your Appium Desktop running?", cs), e);
} else {
throw new HarnessException("Android driver didn't start, please check the connect to server", e);
}
}
});
}
@Override
public void captureVideo(String videoFileName) {
if (!checkVideoRecord()) {
return;
}
String videoPath = store.getConfig("appium.video.path", "target/videos");
File file = Paths.get(videoPath, videoFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, Base64.getDecoder().decode(driver.stopRecordingScreen()));
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + videoFileName + " to " + videoPath, e);
}
}
@Override
public void resetApp() {
driver.resetApp();
driver.activateApp(bundleId);
}
@Override
public void close() {
driver.quit();
}
public void hideAndroidKeyboard() {
driver.hideKeyboard();
}
private AndroidDriver startRecording(AndroidDriver driver) {
if (checkVideoRecord()) {
driver.startRecordingScreen();
}
return driver;
}
private String getUiAutomator() {
boolean uiAutomator = Boolean.parseBoolean(store.getConfig("appium.android.uiAutomator", "false"));
return uiAutomator ? AutomationName.ANDROID_UIAUTOMATOR2 : null;
}
public void pressAndroidKeys(AndroidKey... keys) {
Arrays.stream(keys)
.forEach(key -> getDriver().pressKey(new KeyEvent(key)));
}
}

View File

@ -1,19 +1,19 @@
package cz.moneta.test.harness.connectors.mobile.ios;
public enum HideIosKeyboardButton {
DONE("Done"),
GO("Go"),
ENTER("Enter"),
RETURN("Return"),
NONE("");
private final String value;
HideIosKeyboardButton(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
package cz.moneta.test.harness.connectors.mobile.ios;
public enum HideIosKeyboardButton {
DONE("Done"),
GO("Go"),
ENTER("Enter"),
RETURN("Return"),
NONE("");
private final String value;
HideIosKeyboardButton(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}

View File

@ -1,134 +1,134 @@
package cz.moneta.test.harness.connectors.mobile.ios;
import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.remote.AutomationName;
import io.appium.java_client.remote.IOSMobileCapabilityType;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class IosConnector extends AppiumMobileConnector<IOSDriver> {
private IOSDriver iosDriver;
public IosConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String deviceName, String udid, String bundleId, String appBinaryPath) {
super(defaultLookup, store, host, appBinaryPath);
this.udid = udid;
this.bundleId = bundleId;
this.deviceName = deviceName;
this.iosDriver = initDriver();
}
@Override
protected IOSDriver getDriver() {
return iosDriver;
}
private IOSDriver initDriver() {
return startDriver()
.map(this::startRecording)
.flatMap(d -> Stream.<Supplier<Optional<IOSDriver>>>of(
() -> launchExistingApp(d))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst())
.orElseThrow(() -> new HarnessException("App was not started"));
}
private Optional<IOSDriver> startDriver() {
return Optional.ofNullable(host)
.map(cs -> {
DesiredCapabilities capabilities = new DesiredCapabilities();
setDesiredCapabilities(Platform.IOS, deviceName, udid, AutomationName.IOS_XCUI_TEST, capabilities);
mobileLanguage(capabilities, "en_US", "cs_CZ");
capabilities.setCapability(IOSMobileCapabilityType.BUNDLE_ID, this.bundleId);
try {
return new IOSDriver(new URL(cs), capabilities);
} catch (MalformedURLException e) {
throw new HarnessException(String.format("Appium connection to IOS device is invalid: %s", cs), e);
} catch (WebDriverException e) {
if (e.getCause() instanceof ConnectException) {
throw new HarnessException(String.format("Cannot connect to Appium on %s. Is your Appium Desktop running?", cs), e);
} else {
throw new HarnessException("Ios driver didn't start, please check the connect to server", e);
}
}
});
}
@Override
public void captureVideo(String videoFileName) {
if (!checkVideoRecord()) {
return;
}
String videoPath = store.getConfig("appium.video.path", "target/videos");
File file = Paths.get(videoPath, videoFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, Base64.getDecoder().decode(iosDriver.stopRecordingScreen()));
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + videoFileName + " to " + videoPath, e);
}
}
private IOSDriver startRecording(IOSDriver driver) {
if (checkVideoRecord()) {
driver.startRecordingScreen();
}
return driver;
}
@Override
public void resetApp() {
iosDriver.removeApp(bundleId);
iosDriver.installApp(appBinaryPath);
iosDriver.activateApp(bundleId);
}
@Override
public void close() {
iosDriver.closeApp();
}
public void hideIosKeyboard(HideIosKeyboardButton hideIosKeyboardButton) {
click(hideIosKeyboardButton.getValue(), MobileLookup.ID);
}
public void executeScrollScript(String name, String direction) {
Map<String, Object> args = new HashMap<>();
args.put("direction", direction);
args.put("name", name);
Optional.of(getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(script -> {
try {
script.executeScript("mobile: scroll", args);
} catch (NoSuchElementException e) {
throw new HarnessException("Element " + name + " is not present");
}
});
}
//TODO add in the future
public void pressIosKeys() {
}
}
package cz.moneta.test.harness.connectors.mobile.ios;
import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.remote.AutomationName;
import io.appium.java_client.remote.IOSMobileCapabilityType;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class IosConnector extends AppiumMobileConnector<IOSDriver> {
private IOSDriver iosDriver;
public IosConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String deviceName, String udid, String bundleId, String appBinaryPath) {
super(defaultLookup, store, host, appBinaryPath);
this.udid = udid;
this.bundleId = bundleId;
this.deviceName = deviceName;
this.iosDriver = initDriver();
}
@Override
protected IOSDriver getDriver() {
return iosDriver;
}
private IOSDriver initDriver() {
return startDriver()
.map(this::startRecording)
.flatMap(d -> Stream.<Supplier<Optional<IOSDriver>>>of(
() -> launchExistingApp(d))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst())
.orElseThrow(() -> new HarnessException("App was not started"));
}
private Optional<IOSDriver> startDriver() {
return Optional.ofNullable(host)
.map(cs -> {
DesiredCapabilities capabilities = new DesiredCapabilities();
setDesiredCapabilities(Platform.IOS, deviceName, udid, AutomationName.IOS_XCUI_TEST, capabilities);
mobileLanguage(capabilities, "en_US", "cs_CZ");
capabilities.setCapability(IOSMobileCapabilityType.BUNDLE_ID, this.bundleId);
try {
return new IOSDriver(new URL(cs), capabilities);
} catch (MalformedURLException e) {
throw new HarnessException(String.format("Appium connection to IOS device is invalid: %s", cs), e);
} catch (WebDriverException e) {
if (e.getCause() instanceof ConnectException) {
throw new HarnessException(String.format("Cannot connect to Appium on %s. Is your Appium Desktop running?", cs), e);
} else {
throw new HarnessException("Ios driver didn't start, please check the connect to server", e);
}
}
});
}
@Override
public void captureVideo(String videoFileName) {
if (!checkVideoRecord()) {
return;
}
String videoPath = store.getConfig("appium.video.path", "target/videos");
File file = Paths.get(videoPath, videoFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, Base64.getDecoder().decode(iosDriver.stopRecordingScreen()));
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + videoFileName + " to " + videoPath, e);
}
}
private IOSDriver startRecording(IOSDriver driver) {
if (checkVideoRecord()) {
driver.startRecordingScreen();
}
return driver;
}
@Override
public void resetApp() {
iosDriver.removeApp(bundleId);
iosDriver.installApp(appBinaryPath);
iosDriver.activateApp(bundleId);
}
@Override
public void close() {
iosDriver.closeApp();
}
public void hideIosKeyboard(HideIosKeyboardButton hideIosKeyboardButton) {
click(hideIosKeyboardButton.getValue(), MobileLookup.ID);
}
public void executeScrollScript(String name, String direction) {
Map<String, Object> args = new HashMap<>();
args.put("direction", direction);
args.put("name", name);
Optional.of(getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(script -> {
try {
script.executeScript("mobile: scroll", args);
} catch (NoSuchElementException e) {
throw new HarnessException("Element " + name + " is not present");
}
});
}
//TODO add in the future
public void pressIosKeys() {
}
}

View File

@ -1,62 +1,62 @@
package cz.moneta.test.harness.connectors.rest;
import cz.moneta.test.harness.connectors.Connector;
import org.apache.logging.log4j.jul.LogManager;
import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.DEFAULT_REST_READ_TIMEOUT;
public abstract class BaseRestConnector implements Connector {
protected Client createHttpClient(String loggerName) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
return createHttpClient(loggerName, DEFAULT_REST_READ_TIMEOUT);
}
protected Client createHttpClient(String loggerName, long readTimeout) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustedStore.load(getInputStream("keystores/mb_root"), "changeit".toCharArray());
keyStore.load(getInputStream("keystores/api_gw_client.p12"), "changeit".toCharArray());
ClientConfig config = new ClientConfig()
.register(JacksonFeature.class)
.register(JacksonJsonProvider.class)
.register(MultiPartFeature.class)
.register(new LoggingFeature(LogManager.getLogManager().getLogger(loggerName),
Level.FINE, LoggingFeature.Verbosity.PAYLOAD_ANY, 1024 * 1024))
.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // TODO: test it, this line may affect all REST calls
return ClientBuilder.newBuilder()
.withConfig(config)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS)
.sslContext(SslConfigurator.newInstance()
.trustStore(trustedStore)
.keyStore(keyStore)
.keyStorePassword("changeit".toCharArray())
.createSSLContext())
.build();
}
public InputStream getInputStream(String name) {
return this.getClass().getClassLoader().getResourceAsStream(name);
}
}
package cz.moneta.test.harness.connectors.rest;
import cz.moneta.test.harness.connectors.Connector;
import org.apache.logging.log4j.jul.LogManager;
import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.DEFAULT_REST_READ_TIMEOUT;
public abstract class BaseRestConnector implements Connector {
protected Client createHttpClient(String loggerName) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
return createHttpClient(loggerName, DEFAULT_REST_READ_TIMEOUT);
}
protected Client createHttpClient(String loggerName, long readTimeout) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustedStore.load(getInputStream("keystores/mb_root"), "changeit".toCharArray());
keyStore.load(getInputStream("keystores/api_gw_client.p12"), "changeit".toCharArray());
ClientConfig config = new ClientConfig()
.register(JacksonFeature.class)
.register(JacksonJsonProvider.class)
.register(MultiPartFeature.class)
.register(new LoggingFeature(LogManager.getLogManager().getLogger(loggerName),
Level.FINE, LoggingFeature.Verbosity.PAYLOAD_ANY, 1024 * 1024))
.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // TODO: test it, this line may affect all REST calls
return ClientBuilder.newBuilder()
.withConfig(config)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS)
.sslContext(SslConfigurator.newInstance()
.trustStore(trustedStore)
.keyStore(keyStore)
.keyStorePassword("changeit".toCharArray())
.createSSLContext())
.build();
}
public InputStream getInputStream(String name) {
return this.getClass().getClassLoader().getResourceAsStream(name);
}
}

View File

@ -1,38 +1,38 @@
package cz.moneta.test.harness.connectors.rest;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.Response;
import java.util.Map;
/**
* Rest connector returning extendable object. Now the response contains body and response headers.
*/
public class ExtendedRestConnector extends SimpleRestConnector implements RestConnector {
public ExtendedRestConnector(String endpointUrl, String loggerName) {
super(endpointUrl, loggerName);
}
public ExtendedRestConnector(String endpointUrl, String loggerName, long readTimeout) {
super(endpointUrl, loggerName, readTimeout);
}
@Override
public <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(String path, Entity<?> request,
Class<T> responseType,
Map<String, Object> headers) {
try {
Response response = doPost(path, request, headers);
return Pair.of(response.getStatus(), new ExtendedRestResponse<>(response.readEntity(responseType),
response.getHeaders()));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
}
}
}
package cz.moneta.test.harness.connectors.rest;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.Response;
import java.util.Map;
/**
* Rest connector returning extendable object. Now the response contains body and response headers.
*/
public class ExtendedRestConnector extends SimpleRestConnector implements RestConnector {
public ExtendedRestConnector(String endpointUrl, String loggerName) {
super(endpointUrl, loggerName);
}
public ExtendedRestConnector(String endpointUrl, String loggerName, long readTimeout) {
super(endpointUrl, loggerName, readTimeout);
}
@Override
public <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(String path, Entity<?> request,
Class<T> responseType,
Map<String, Object> headers) {
try {
Response response = doPost(path, request, headers);
return Pair.of(response.getStatus(), new ExtendedRestResponse<>(response.readEntity(responseType),
response.getHeaders()));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
}
}
}

View File

@ -1,25 +1,25 @@
package cz.moneta.test.harness.connectors.rest;
import jakarta.ws.rs.core.MultivaluedMap;
/**
* Extendable response object from {@link ExtendedRestConnector}.
*/
public class ExtendedRestResponse<T> {
private T responseBody;
private MultivaluedMap<String, Object> responseHeaders;
public ExtendedRestResponse(T responseBody, MultivaluedMap<String, Object> responseHeaders) {
this.responseBody = responseBody;
this.responseHeaders = responseHeaders;
}
public T getResponseBody() {
return responseBody;
}
public MultivaluedMap<String, Object> getResponseHeaders() {
return responseHeaders;
}
}
package cz.moneta.test.harness.connectors.rest;
import jakarta.ws.rs.core.MultivaluedMap;
/**
* Extendable response object from {@link ExtendedRestConnector}.
*/
public class ExtendedRestResponse<T> {
private T responseBody;
private MultivaluedMap<String, Object> responseHeaders;
public ExtendedRestResponse(T responseBody, MultivaluedMap<String, Object> responseHeaders) {
this.responseBody = responseBody;
this.responseHeaders = responseHeaders;
}
public T getResponseBody() {
return responseBody;
}
public MultivaluedMap<String, Object> getResponseHeaders() {
return responseHeaders;
}
}

View File

@ -1,40 +1,40 @@
package cz.moneta.test.harness.connectors.rest;
import java.util.Map;
public class RemoteRestCallRequest {
private String path;
private Object request;
private Map<String, Object> getProperties;
private Map<String, Object> headers;
public RemoteRestCallRequest() {}
public RemoteRestCallRequest(String path, Map<String, Object> getProperties, Map<String, Object> headers) {
this.path = path;
this.getProperties = getProperties;
this.headers = headers;
}
public RemoteRestCallRequest(String path, Object request, Map<String, Object> headers) {
this.path = path;
this.request = request;
this.headers = headers;
}
public String getPath() {
return path;
}
public Object getRequest() {
return request;
}
public Map<String, Object> getGetProperties() {
return getProperties;
}
public Map<String, Object> getHeaders() {
return headers;
}
}
package cz.moneta.test.harness.connectors.rest;
import java.util.Map;
public class RemoteRestCallRequest {
private String path;
private Object request;
private Map<String, Object> getProperties;
private Map<String, Object> headers;
public RemoteRestCallRequest() {}
public RemoteRestCallRequest(String path, Map<String, Object> getProperties, Map<String, Object> headers) {
this.path = path;
this.getProperties = getProperties;
this.headers = headers;
}
public RemoteRestCallRequest(String path, Object request, Map<String, Object> headers) {
this.path = path;
this.request = request;
this.headers = headers;
}
public String getPath() {
return path;
}
public Object getRequest() {
return request;
}
public Map<String, Object> getGetProperties() {
return getProperties;
}
public Map<String, Object> getHeaders() {
return headers;
}
}

View File

@ -1,12 +1,12 @@
package cz.moneta.test.harness.connectors.rest;
/**
* Response handler which enables response verification and post-processing
*
* @param <I> original request invocation (i.e. the request call)
* @param <RESP> the original response against which the handling is performed
* @see cz.moneta.test.harness.connectors.wso2.TokenRenewalResponseHandler
*/
public interface ResponseHandler<I, RESP> {
RESP handle(I invocation, RESP response);
}
package cz.moneta.test.harness.connectors.rest;
/**
* Response handler which enables response verification and post-processing
*
* @param <I> original request invocation (i.e. the request call)
* @param <RESP> the original response against which the handling is performed
* @see cz.moneta.test.harness.connectors.wso2.TokenRenewalResponseHandler
*/
public interface ResponseHandler<I, RESP> {
RESP handle(I invocation, RESP response);
}

View File

@ -1,40 +1,40 @@
package cz.moneta.test.harness.connectors.rest;
import cz.moneta.test.harness.performance.PerformanceAware;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.function.Function;
public interface RestConnector extends PerformanceAware {
<T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> repsonseType, Map<String, Object> headers);
RestConnector registerResponseHandler(
ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> responseHandler);
/**
* Return response body with response headers.
*/
default <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(
String path, Entity<?> request,
Class<T> responseType, Map<String,
Object> headers) {
throw new IllegalStateException("Not implemented");
}
@Override
default String getPerformanceReportKey(Object proxy, Method method, Object[] args) {
return (String) args[0];
}
}
package cz.moneta.test.harness.connectors.rest;
import cz.moneta.test.harness.performance.PerformanceAware;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.function.Function;
public interface RestConnector extends PerformanceAware {
<T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> repsonseType, Map<String, Object> headers);
RestConnector registerResponseHandler(
ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> responseHandler);
/**
* Return response body with response headers.
*/
default <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(
String path, Entity<?> request,
Class<T> responseType, Map<String,
Object> headers) {
throw new IllegalStateException("Not implemented");
}
@Override
default String getPerformanceReportKey(Object proxy, Method method, Object[] args) {
return (String) args[0];
}
}

View File

@ -1,155 +1,155 @@
package cz.moneta.test.harness.connectors.rest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.DEFAULT_REST_READ_TIMEOUT;
import static cz.moneta.test.harness.support.rest.RestUtils.toMultiMap;
public class SimpleRestConnector extends BaseRestConnector implements RestConnector {
protected WebTarget target;
protected List<ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response>> responseHandlers = new ArrayList<>();
public SimpleRestConnector(String endpointUrl, String loggerName) {
this(endpointUrl, loggerName, DEFAULT_REST_READ_TIMEOUT);
}
public SimpleRestConnector(String endpointUrl, String loggerName, long readTimeout) {
try {
Client client = createHttpClient(loggerName, readTimeout);
target = client.target(endpointUrl);
} catch (Exception e) {
throw new RuntimeException("REST connector is not configured properly", e);
}
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers) {
try {
Invocation.Builder invocation = properties.entrySet().stream()
.reduce(target, (t, e) -> t.queryParam(e.getKey(), e.getValue()), (t1, t2) -> t1)
.path(path)
.request()
.headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache"));
Response response = responseHandlers.stream()
.reduce(
invocation.get(),
(resp, h) -> h.handle(Pair.of(invocation, Invocation.Builder::get), resp),
(h1, h2) -> h1);
return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
} catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
}
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
try {
Response response = doPost(path, request, headers);
return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
} catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
}
}
protected Response doPost(String path, Entity<?> request, Map<String, Object> headers) {
Invocation.Builder invocation = target.path(path)
.request()
.headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache"));
return responseHandlers.stream()
.reduce(
invocation.post(request),
(resp, h) -> h.handle(Pair.of(invocation, i -> i.post(request)), resp),
(h1, h2) -> h1);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers) {
try {
Invocation.Builder invocation = properties.entrySet().stream()
.reduce(target, (t, e) -> t.queryParam(e.getKey(), e.getValue()), (t1, t2) -> t1)
.path(path)
.request()
.headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache"));
Response response = responseHandlers.stream()
.reduce(
invocation.delete(),
(resp, h) -> h.handle(Pair.of(invocation, Invocation.Builder::delete), resp),
(h1, h2) -> h1);
return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
} catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
}
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
try {
Invocation.Builder invocation = target.path(path)
.request()
.headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache"));
Response response = responseHandlers.stream()
.reduce(
invocation.method("PATCH", request),
(resp, h) -> h.handle(Pair.of(invocation, i -> i.method("PATCH", request)), resp),
(h1, h2) -> h1);
return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
} catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
}
}
@Override
public RestConnector registerResponseHandler(ResponseHandler<Pair<Invocation.Builder,
Function<Invocation.Builder, Response>>, Response> responseHandler) {
responseHandlers.add(responseHandler);
return this;
}
protected <T> T throwWithResponseText(InternalServerErrorException serverError) {
String response = "N/A";
try {
response = IOUtils.toString((InputStream) serverError.getResponse().getEntity(), Charset.defaultCharset());
} catch (IOException ignore) {
}
throw new AssertionError(String.format("Request failed. HTTP status: %d, response: %s", serverError.getResponse().getStatus(), response));
}
}
package cz.moneta.test.harness.connectors.rest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.DEFAULT_REST_READ_TIMEOUT;
import static cz.moneta.test.harness.support.rest.RestUtils.toMultiMap;
public class SimpleRestConnector extends BaseRestConnector implements RestConnector {
protected WebTarget target;
protected List<ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response>> responseHandlers = new ArrayList<>();
public SimpleRestConnector(String endpointUrl, String loggerName) {
this(endpointUrl, loggerName, DEFAULT_REST_READ_TIMEOUT);
}
public SimpleRestConnector(String endpointUrl, String loggerName, long readTimeout) {
try {
Client client = createHttpClient(loggerName, readTimeout);
target = client.target(endpointUrl);
} catch (Exception e) {
throw new RuntimeException("REST connector is not configured properly", e);
}
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers) {
try {
Invocation.Builder invocation = properties.entrySet().stream()
.reduce(target, (t, e) -> t.queryParam(e.getKey(), e.getValue()), (t1, t2) -> t1)
.path(path)
.request()
.headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache"));
Response response = responseHandlers.stream()
.reduce(
invocation.get(),
(resp, h) -> h.handle(Pair.of(invocation, Invocation.Builder::get), resp),
(h1, h2) -> h1);
return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
} catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
}
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
try {
Response response = doPost(path, request, headers);
return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
} catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
}
}
protected Response doPost(String path, Entity<?> request, Map<String, Object> headers) {
Invocation.Builder invocation = target.path(path)
.request()
.headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache"));
return responseHandlers.stream()
.reduce(
invocation.post(request),
(resp, h) -> h.handle(Pair.of(invocation, i -> i.post(request)), resp),
(h1, h2) -> h1);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers) {
try {
Invocation.Builder invocation = properties.entrySet().stream()
.reduce(target, (t, e) -> t.queryParam(e.getKey(), e.getValue()), (t1, t2) -> t1)
.path(path)
.request()
.headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache"));
Response response = responseHandlers.stream()
.reduce(
invocation.delete(),
(resp, h) -> h.handle(Pair.of(invocation, Invocation.Builder::delete), resp),
(h1, h2) -> h1);
return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
} catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
}
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
try {
Invocation.Builder invocation = target.path(path)
.request()
.headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache"));
Response response = responseHandlers.stream()
.reduce(
invocation.method("PATCH", request),
(resp, h) -> h.handle(Pair.of(invocation, i -> i.method("PATCH", request)), resp),
(h1, h2) -> h1);
return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError);
} catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
}
}
@Override
public RestConnector registerResponseHandler(ResponseHandler<Pair<Invocation.Builder,
Function<Invocation.Builder, Response>>, Response> responseHandler) {
responseHandlers.add(responseHandler);
return this;
}
protected <T> T throwWithResponseText(InternalServerErrorException serverError) {
String response = "N/A";
try {
response = IOUtils.toString((InputStream) serverError.getResponse().getEntity(), Charset.defaultCharset());
} catch (IOException ignore) {
}
throw new AssertionError(String.format("Request failed. HTTP status: %d, response: %s", serverError.getResponse().getStatus(), response));
}
}

View File

@ -1,66 +1,66 @@
package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.Alert;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.support.ui.FluentWait;
import java.time.Duration;
import java.util.function.Consumer;
import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent;
import static org.openqa.selenium.support.ui.ExpectedConditions.not;
public class Alerts {
private final SeleniumWebConnector connector;
public Alerts(SeleniumWebConnector connector) {
this.connector = connector;
}
public void dismissAlert(int timeout) {
handleAlert(timeout, Alert::dismiss);
}
public void acceptAlert(int timeout) {
handleAlert(timeout, Alert::accept);
}
public boolean isAlertPresent(int timeout) {
try {
waitForAlert(timeout);
return true;
} catch (TimeoutException e) {
return false;
}
}
private void handleAlert(int timeout, Consumer<Alert> action) {
waitForAlert(timeout);
action.accept(connector.getDriver()
.switchTo()
.alert());
new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(timeout))
.pollingEvery(Duration.ofMillis(300))
.withMessage("Alert is still present after " + timeout + " seconds timeout.")
.until(driver -> not(alertIsPresent())).apply(connector.getDriver());
}
public String getAlertText(int timeout) {
waitForAlert(timeout);
return connector.getDriver()
.switchTo()
.alert()
.getText();
}
private void waitForAlert(int timeout) {
new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(timeout))
.pollingEvery(Duration.ofMillis(300))
.withMessage("Alert is not present after " + timeout + " seconds timeout.")
.until(driver -> alertIsPresent().apply(connector.getDriver()));
}
}
package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.Alert;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.support.ui.FluentWait;
import java.time.Duration;
import java.util.function.Consumer;
import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent;
import static org.openqa.selenium.support.ui.ExpectedConditions.not;
public class Alerts {
private final SeleniumWebConnector connector;
public Alerts(SeleniumWebConnector connector) {
this.connector = connector;
}
public void dismissAlert(int timeout) {
handleAlert(timeout, Alert::dismiss);
}
public void acceptAlert(int timeout) {
handleAlert(timeout, Alert::accept);
}
public boolean isAlertPresent(int timeout) {
try {
waitForAlert(timeout);
return true;
} catch (TimeoutException e) {
return false;
}
}
private void handleAlert(int timeout, Consumer<Alert> action) {
waitForAlert(timeout);
action.accept(connector.getDriver()
.switchTo()
.alert());
new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(timeout))
.pollingEvery(Duration.ofMillis(300))
.withMessage("Alert is still present after " + timeout + " seconds timeout.")
.until(driver -> not(alertIsPresent())).apply(connector.getDriver());
}
public String getAlertText(int timeout) {
waitForAlert(timeout);
return connector.getDriver()
.switchTo()
.alert()
.getText();
}
private void waitForAlert(int timeout) {
new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(timeout))
.pollingEvery(Duration.ofMillis(300))
.withMessage("Alert is not present after " + timeout + " seconds timeout.")
.until(driver -> alertIsPresent().apply(connector.getDriver()));
}
}

View File

@ -1,67 +1,67 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Clickable;
import cz.moneta.test.harness.support.web.Lookup;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.Optional;
public class Clicks {
private final SeleniumWebConnector connector;
private static final int CLICKABLE_TIMEOUT = 5;
private final static String DOUBLE_CLICK_JS = "var evt = document.createEvent('MouseEvents');"
+ "evt.initMouseEvent('dblclick',true, true, window, 10, 0, 0, 0, 0, false, false, false, false, 0,null);"
+ "arguments[0].dispatchEvent(evt);";
private final static String JS_CLICK_SCRIPT = "console.debug('jsClick start'); " +
"setTimeout(function(elem){ elem.click();}, 0, arguments[0]); " +
"console.debug('jsClick end');";
public Clicks(SeleniumWebConnector connector) {
this.connector = connector;
}
public void click(Clickable clickable, Lookup lookup) {
WebElement element = connector.waits().waitForLazyElement(clickable.getPath(), lookup);
isClickable(element).click();
}
protected WebElement isClickable(WebElement webElement) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(CLICKABLE_TIMEOUT));
return wait.until(ExpectedConditions.elementToBeClickable(webElement));
}
protected WebElement isClickable(Clickable clickable, Lookup lookup) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(CLICKABLE_TIMEOUT));
return wait.until(ExpectedConditions.elementToBeClickable(connector.resolveLookup(lookup).apply(clickable.getPath())));
}
public void doubleClick(Clickable clickable, Lookup lookup) {
Actions actions = new Actions(connector.getDriver());
WebElement element = connector.waits().waitForLazyElement(clickable.getPath(), lookup);
actions.doubleClick(element);
}
public void jsDoubleClick(Clickable clickable, Lookup lookup) {
Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(e -> e.executeScript(DOUBLE_CLICK_JS,
connector.waits().waitForLazyElement(clickable.getPath(), lookup)));
}
public void jsClick(Clickable clickable, Lookup lookup) {
Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(e -> e.executeScript(JS_CLICK_SCRIPT,
connector.waits().waitForLazyElement(clickable.getPath(), lookup)));
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Clickable;
import cz.moneta.test.harness.support.web.Lookup;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.Optional;
public class Clicks {
private final SeleniumWebConnector connector;
private static final int CLICKABLE_TIMEOUT = 5;
private final static String DOUBLE_CLICK_JS = "var evt = document.createEvent('MouseEvents');"
+ "evt.initMouseEvent('dblclick',true, true, window, 10, 0, 0, 0, 0, false, false, false, false, 0,null);"
+ "arguments[0].dispatchEvent(evt);";
private final static String JS_CLICK_SCRIPT = "console.debug('jsClick start'); " +
"setTimeout(function(elem){ elem.click();}, 0, arguments[0]); " +
"console.debug('jsClick end');";
public Clicks(SeleniumWebConnector connector) {
this.connector = connector;
}
public void click(Clickable clickable, Lookup lookup) {
WebElement element = connector.waits().waitForLazyElement(clickable.getPath(), lookup);
isClickable(element).click();
}
protected WebElement isClickable(WebElement webElement) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(CLICKABLE_TIMEOUT));
return wait.until(ExpectedConditions.elementToBeClickable(webElement));
}
protected WebElement isClickable(Clickable clickable, Lookup lookup) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(CLICKABLE_TIMEOUT));
return wait.until(ExpectedConditions.elementToBeClickable(connector.resolveLookup(lookup).apply(clickable.getPath())));
}
public void doubleClick(Clickable clickable, Lookup lookup) {
Actions actions = new Actions(connector.getDriver());
WebElement element = connector.waits().waitForLazyElement(clickable.getPath(), lookup);
actions.doubleClick(element);
}
public void jsDoubleClick(Clickable clickable, Lookup lookup) {
Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(e -> e.executeScript(DOUBLE_CLICK_JS,
connector.waits().waitForLazyElement(clickable.getPath(), lookup)));
}
public void jsClick(Clickable clickable, Lookup lookup) {
Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(e -> e.executeScript(JS_CLICK_SCRIPT,
connector.waits().waitForLazyElement(clickable.getPath(), lookup)));
}
}

View File

@ -1,30 +1,30 @@
package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.Cookie;
import java.util.Date;
import java.util.Optional;
public class Cookies {
private final SeleniumWebConnector connector;
public Cookies(SeleniumWebConnector connector) {
this.connector = connector;
}
public String getCookie(String name) {
return Optional.ofNullable(connector.getDriver().manage().getCookieNamed(name))
.map(Cookie::getValue)
.orElse(null);
}
public void addCookie(String name, String value, String domain, String path, Date expiry, boolean isSecure, boolean isHttpOnly) {
Cookie cookie = new Cookie(name, value, domain, path, expiry, isSecure, isHttpOnly);
connector.getDriver().manage().addCookie(cookie);
}
public void addCookie(Cookie cookie) {
connector.getDriver().manage().addCookie(cookie);
}
package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.Cookie;
import java.util.Date;
import java.util.Optional;
public class Cookies {
private final SeleniumWebConnector connector;
public Cookies(SeleniumWebConnector connector) {
this.connector = connector;
}
public String getCookie(String name) {
return Optional.ofNullable(connector.getDriver().manage().getCookieNamed(name))
.map(Cookie::getValue)
.orElse(null);
}
public void addCookie(String name, String value, String domain, String path, Date expiry, boolean isSecure, boolean isHttpOnly) {
Cookie cookie = new Cookie(name, value, domain, path, expiry, isSecure, isHttpOnly);
connector.getDriver().manage().addCookie(cookie);
}
public void addCookie(Cookie cookie) {
connector.getDriver().manage().addCookie(cookie);
}
}

View File

@ -1,255 +1,255 @@
package cz.moneta.test.harness.connectors.web;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.exception.HarnessConfigurationException;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.selenium.ClasspathFileDetector;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.*;
import org.openqa.selenium.logging.*;
import org.openqa.selenium.manager.SeleniumManager;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY;
public class DriverUtils {
protected static final Set<String> LOG_TYPES = Stream.of(LogType.BROWSER, LogType.CLIENT, LogType.DRIVER,
LogType.PERFORMANCE, LogType.PROFILER, LogType.SERVER).collect(Collectors.toSet());
private static final Logger logger = LogManager.getLogger(DriverUtils.class);
private final SeleniumWebConnector connector;
public DriverUtils(SeleniumWebConnector connector) {
this.connector = connector;
}
public String takeSnapshot(String snapshotFileName) {
String snapshotsPath = connector.getStore().getConfig("selenium.snapshots.path", "target/screenshots");
File file = Paths.get(snapshotsPath, snapshotFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, ((TakesScreenshot) connector.getDriver()).getScreenshotAs(OutputType.BYTES));
} catch (IOException e) {
throw new IllegalStateException("Unable to write snapshot " + snapshotFileName + " to " + snapshotsPath, e);
} catch (WebDriverException e2) { // An unexpected error swallows why the test failed
logger.error("Error during getting snapshot: " + e2.getMessage());
e2.printStackTrace();
}
return file.getAbsolutePath();
}
public String captureLog(String logFileName, String logType) {
String path = connector.getStore().getConfig("selenium.logs.path", "target/logs");
Logs logs = connector.getDriver()
.manage()
.logs();
LogEntries logEntriesForType = null;
try {
logEntriesForType = logs.get(logType);
} catch (Exception wde) {
logger.warn(() -> "We were not able to get log entries for log type: " + logType);
}
if (logEntriesForType != null) {
List<LogEntry> logEntries = logEntriesForType.getAll();
try {
File file = Paths.get(path, logFileName, ".log").toFile();
FileUtils.writeLines(file, logEntries);
return file.getAbsolutePath();
} catch (IOException e) {
throw new IllegalStateException("Unable to write log " + logFileName + " to " + path, e);
}
}
return null;
}
public void captureDom(String domFileName) {
String path = connector.getStore().getConfig("selenium.dom.path", "target/dom");
File file = Paths.get(path, domFileName).toFile();
try {
FileUtils.write(file, connector.getDriver().getPageSource());
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + domFileName + " to " + path, e);
} catch (WebDriverException e2) { // An unexpected error swallows why the test failed
logger.error("Error during getting DOM: " + e2.getMessage());
e2.printStackTrace();
}
}
/**
* Add platform/browser/driver independent capabilities.
*
* @return DesiredCapabilities with default capabilities added
*/
protected void addDefaultDesiredCapabilities(MutableCapabilities capabilities) {
LoggingPreferences logs = new LoggingPreferences();
LOG_TYPES.forEach(lt -> logs.enable(lt, Level.ALL));
//capabilities.setCapability(CapabilityType.LOGGING_PREFS, logs); // TODO: vyřešit
java.util.logging.Logger.getLogger(RemoteWebDriver.class.getName()).setLevel(Level.INFO);
java.util.logging.Logger.getLogger(SeleniumManager.class.getName()).setLevel(Level.INFO);
}
/**
* Allows to configure additional capabilities.
*
* @param capabilities collection of capabilities to be added to
* @param key key to use for capabilities lookup in configuration
*/
protected void addAdditionalDesiredCapabilities(MutableCapabilities capabilities, String key) {
String additionalCapabilities = connector.getStore().getConfig(key);
if (additionalCapabilities != null) {
Arrays.stream(additionalCapabilities.split(",")).forEach(
(item) -> {
String[] keyValue = item.split("=");
String value = keyValue[1];
if (!keyValue[0].equals("applicationName")) {
if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
capabilities.setCapability(keyValue[0], Boolean.parseBoolean(value));
} else {
capabilities.setCapability(keyValue[0], value);
}
}
}
);
}
}
WebDriver initializeRemoteWebDriver(String hubUrl) {
try {
// TODO: timeout připojení k browseru
// HttpClient.Factory factory = HttpClient.Factory.createDefault();
// HttpClient.Builder builder = factory.builder()
// .connectionTimeout(Duration.ofSeconds(10));
// HttpClient.Factory clientFactory = new HttpClient.Factory() {
// @Override
// public HttpClient.Builder builder() {
// return new HttpClient.Builder() {
// @Override
// public HttpClient createClient(URL url) {
// return builder.createClient(url);
// }
// };
// }
// @Override
// public void cleanupIdleClients() {
// factory.cleanupIdleClients();
// }
// };
// HttpCommandExecutor executor = new HttpCommandExecutor(new HashMap<>(), new URL(hubUrl), clientFactory);
// RemoteWebDriver remoteWebDriver = new RemoteWebDriver(executor, connector.getDesiredRemoteCapabilities());
RemoteWebDriver remoteWebDriver = new RemoteWebDriver(new URL(hubUrl), connector.getDesiredRemoteCapabilities());
remoteWebDriver.setFileDetector(new ClasspathFileDetector());
String sessionId = remoteWebDriver.getSessionId().toString();
logger.info(() -> String.format("New Selenium web driver session %s started at node %s", sessionId, getNodeUrl(hubUrl, sessionId)));
return remoteWebDriver;
} catch (Exception e) {
logger.error(() -> "Failed to initialize remote web driver with url " + hubUrl, e);
return null;
}
}
private String getNodeUrl(String hubUrl, String sessionId) {
HttpURLConnection gridApiConnection = null;
try {
URL url = new URL(hubUrl);
URL gridGraphQLUrl = new URL(String.format("http://%s:%d/graphql", url.getHost(), url.getPort()));
Map<String, Object> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
Pair<Integer, String> response = new SimpleRestConnector(gridGraphQLUrl.toString(), "SeleniumGraphQL")
.post("",
Entity.json("{\"query\":\"{ session (id: \\\"" + sessionId + "\\\") { uri, nodeUri } } \"}"),
new GenericType<>(String.class),
headers);
JsonObject gridResponse = JsonParser.parseString(response.getRight().toString()).getAsJsonObject();
String nodeUrlString = gridResponse.getAsJsonObject("data").getAsJsonObject("session").get("uri").getAsString();
URL nodeUrl = new URL(nodeUrlString);
this.connector.setNodeHost(nodeUrl.getHost());
return nodeUrlString;
} catch (Exception e) {
throw new HarnessException(String.format("Failed to get node info for session %s", sessionId), e);
} finally {
if (gridApiConnection != null) {
try {
gridApiConnection.disconnect();
} catch (Exception e) {
throw new HarnessException("Could not close connection to grid API.", e);
}
}
}
}
public boolean checkIfFileExists(String directory, String filename, int waitInSeconds, boolean deleteFile, boolean useDirectoryListMethod) {
boolean fileExists = false;
// If no directory set as input parameter then get it from config file or System properties.
if (directory == null) {
directory = Stream.of(Optional.ofNullable(System.getProperty(SELENIUM_DOWNLOAD_DIRECTORY)),
Optional.ofNullable(connector.getStore().getConfig(SELENIUM_DOWNLOAD_DIRECTORY)))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElseThrow(() -> new HarnessConfigurationException(("You need to set download directory or configure " + SELENIUM_DOWNLOAD_DIRECTORY + " parameter!")));
}
// File which we try to find
File fileToCheck = FileUtils.getFile(directory, filename);
File folderToCheck = new File(directory);
try {
// Checking if file is downloaded. Try it every second to "waitInSeconds" limit.
for (int i = 0; i <= waitInSeconds; i++) {
// Simulating wait for file download.
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
if (useDirectoryListMethod) {
logger.debug("Checking if file: {} present in directory list: {}.", filename, directory);
fileExists = Arrays.stream(folderToCheck.list()).anyMatch(filename::equals);
} else {
logger.debug("Checking if file: {} exists in directory: {}.", filename, directory);
fileExists = (fileToCheck.exists() || fileToCheck.isFile());
}
if (fileExists) break;
}
// When file exists - log it and delete file
if (fileExists) {
logger.debug("Found file: {}.", fileToCheck.getAbsolutePath());
logger.info("Found expected file: {} in directory {}.", filename, directory);
// For cleaning purposes. (Does not delete all files, ex. in case of wrong filename the downloaded file will remain in directory.)
if (deleteFile) {
logger.debug("Deleting file: {}.", fileToCheck.getAbsolutePath());
Files.delete(fileToCheck.toPath());
}
} else {
throw new HarnessException(String.format("Could not find expected file: %s in directory %s withing the specified timeout", filename, directory));
}
} catch (Exception e) {
throw new HarnessException(String.format("Something went wrong: Could not find expected file: %s in directory %s.", filename, directory), e);
}
return fileExists;
}
}
package cz.moneta.test.harness.connectors.web;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.exception.HarnessConfigurationException;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.selenium.ClasspathFileDetector;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.*;
import org.openqa.selenium.logging.*;
import org.openqa.selenium.manager.SeleniumManager;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY;
public class DriverUtils {
protected static final Set<String> LOG_TYPES = Stream.of(LogType.BROWSER, LogType.CLIENT, LogType.DRIVER,
LogType.PERFORMANCE, LogType.PROFILER, LogType.SERVER).collect(Collectors.toSet());
private static final Logger logger = LogManager.getLogger(DriverUtils.class);
private final SeleniumWebConnector connector;
public DriverUtils(SeleniumWebConnector connector) {
this.connector = connector;
}
public String takeSnapshot(String snapshotFileName) {
String snapshotsPath = connector.getStore().getConfig("selenium.snapshots.path", "target/screenshots");
File file = Paths.get(snapshotsPath, snapshotFileName).toFile();
try {
FileUtils.writeByteArrayToFile(file, ((TakesScreenshot) connector.getDriver()).getScreenshotAs(OutputType.BYTES));
} catch (IOException e) {
throw new IllegalStateException("Unable to write snapshot " + snapshotFileName + " to " + snapshotsPath, e);
} catch (WebDriverException e2) { // An unexpected error swallows why the test failed
logger.error("Error during getting snapshot: " + e2.getMessage());
e2.printStackTrace();
}
return file.getAbsolutePath();
}
public String captureLog(String logFileName, String logType) {
String path = connector.getStore().getConfig("selenium.logs.path", "target/logs");
Logs logs = connector.getDriver()
.manage()
.logs();
LogEntries logEntriesForType = null;
try {
logEntriesForType = logs.get(logType);
} catch (Exception wde) {
logger.warn(() -> "We were not able to get log entries for log type: " + logType);
}
if (logEntriesForType != null) {
List<LogEntry> logEntries = logEntriesForType.getAll();
try {
File file = Paths.get(path, logFileName, ".log").toFile();
FileUtils.writeLines(file, logEntries);
return file.getAbsolutePath();
} catch (IOException e) {
throw new IllegalStateException("Unable to write log " + logFileName + " to " + path, e);
}
}
return null;
}
public void captureDom(String domFileName) {
String path = connector.getStore().getConfig("selenium.dom.path", "target/dom");
File file = Paths.get(path, domFileName).toFile();
try {
FileUtils.write(file, connector.getDriver().getPageSource());
} catch (IOException e) {
throw new IllegalStateException("Unable to write " + domFileName + " to " + path, e);
} catch (WebDriverException e2) { // An unexpected error swallows why the test failed
logger.error("Error during getting DOM: " + e2.getMessage());
e2.printStackTrace();
}
}
/**
* Add platform/browser/driver independent capabilities.
*
* @return DesiredCapabilities with default capabilities added
*/
protected void addDefaultDesiredCapabilities(MutableCapabilities capabilities) {
LoggingPreferences logs = new LoggingPreferences();
LOG_TYPES.forEach(lt -> logs.enable(lt, Level.ALL));
//capabilities.setCapability(CapabilityType.LOGGING_PREFS, logs); // TODO: vyřešit
java.util.logging.Logger.getLogger(RemoteWebDriver.class.getName()).setLevel(Level.INFO);
java.util.logging.Logger.getLogger(SeleniumManager.class.getName()).setLevel(Level.INFO);
}
/**
* Allows to configure additional capabilities.
*
* @param capabilities collection of capabilities to be added to
* @param key key to use for capabilities lookup in configuration
*/
protected void addAdditionalDesiredCapabilities(MutableCapabilities capabilities, String key) {
String additionalCapabilities = connector.getStore().getConfig(key);
if (additionalCapabilities != null) {
Arrays.stream(additionalCapabilities.split(",")).forEach(
(item) -> {
String[] keyValue = item.split("=");
String value = keyValue[1];
if (!keyValue[0].equals("applicationName")) {
if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
capabilities.setCapability(keyValue[0], Boolean.parseBoolean(value));
} else {
capabilities.setCapability(keyValue[0], value);
}
}
}
);
}
}
WebDriver initializeRemoteWebDriver(String hubUrl) {
try {
// TODO: timeout připojení k browseru
// HttpClient.Factory factory = HttpClient.Factory.createDefault();
// HttpClient.Builder builder = factory.builder()
// .connectionTimeout(Duration.ofSeconds(10));
// HttpClient.Factory clientFactory = new HttpClient.Factory() {
// @Override
// public HttpClient.Builder builder() {
// return new HttpClient.Builder() {
// @Override
// public HttpClient createClient(URL url) {
// return builder.createClient(url);
// }
// };
// }
// @Override
// public void cleanupIdleClients() {
// factory.cleanupIdleClients();
// }
// };
// HttpCommandExecutor executor = new HttpCommandExecutor(new HashMap<>(), new URL(hubUrl), clientFactory);
// RemoteWebDriver remoteWebDriver = new RemoteWebDriver(executor, connector.getDesiredRemoteCapabilities());
RemoteWebDriver remoteWebDriver = new RemoteWebDriver(new URL(hubUrl), connector.getDesiredRemoteCapabilities());
remoteWebDriver.setFileDetector(new ClasspathFileDetector());
String sessionId = remoteWebDriver.getSessionId().toString();
logger.info(() -> String.format("New Selenium web driver session %s started at node %s", sessionId, getNodeUrl(hubUrl, sessionId)));
return remoteWebDriver;
} catch (Exception e) {
logger.error(() -> "Failed to initialize remote web driver with url " + hubUrl, e);
return null;
}
}
private String getNodeUrl(String hubUrl, String sessionId) {
HttpURLConnection gridApiConnection = null;
try {
URL url = new URL(hubUrl);
URL gridGraphQLUrl = new URL(String.format("http://%s:%d/graphql", url.getHost(), url.getPort()));
Map<String, Object> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
Pair<Integer, String> response = new SimpleRestConnector(gridGraphQLUrl.toString(), "SeleniumGraphQL")
.post("",
Entity.json("{\"query\":\"{ session (id: \\\"" + sessionId + "\\\") { uri, nodeUri } } \"}"),
new GenericType<>(String.class),
headers);
JsonObject gridResponse = JsonParser.parseString(response.getRight().toString()).getAsJsonObject();
String nodeUrlString = gridResponse.getAsJsonObject("data").getAsJsonObject("session").get("uri").getAsString();
URL nodeUrl = new URL(nodeUrlString);
this.connector.setNodeHost(nodeUrl.getHost());
return nodeUrlString;
} catch (Exception e) {
throw new HarnessException(String.format("Failed to get node info for session %s", sessionId), e);
} finally {
if (gridApiConnection != null) {
try {
gridApiConnection.disconnect();
} catch (Exception e) {
throw new HarnessException("Could not close connection to grid API.", e);
}
}
}
}
public boolean checkIfFileExists(String directory, String filename, int waitInSeconds, boolean deleteFile, boolean useDirectoryListMethod) {
boolean fileExists = false;
// If no directory set as input parameter then get it from config file or System properties.
if (directory == null) {
directory = Stream.of(Optional.ofNullable(System.getProperty(SELENIUM_DOWNLOAD_DIRECTORY)),
Optional.ofNullable(connector.getStore().getConfig(SELENIUM_DOWNLOAD_DIRECTORY)))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElseThrow(() -> new HarnessConfigurationException(("You need to set download directory or configure " + SELENIUM_DOWNLOAD_DIRECTORY + " parameter!")));
}
// File which we try to find
File fileToCheck = FileUtils.getFile(directory, filename);
File folderToCheck = new File(directory);
try {
// Checking if file is downloaded. Try it every second to "waitInSeconds" limit.
for (int i = 0; i <= waitInSeconds; i++) {
// Simulating wait for file download.
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
if (useDirectoryListMethod) {
logger.debug("Checking if file: {} present in directory list: {}.", filename, directory);
fileExists = Arrays.stream(folderToCheck.list()).anyMatch(filename::equals);
} else {
logger.debug("Checking if file: {} exists in directory: {}.", filename, directory);
fileExists = (fileToCheck.exists() || fileToCheck.isFile());
}
if (fileExists) break;
}
// When file exists - log it and delete file
if (fileExists) {
logger.debug("Found file: {}.", fileToCheck.getAbsolutePath());
logger.info("Found expected file: {} in directory {}.", filename, directory);
// For cleaning purposes. (Does not delete all files, ex. in case of wrong filename the downloaded file will remain in directory.)
if (deleteFile) {
logger.debug("Deleting file: {}.", fileToCheck.getAbsolutePath());
Files.delete(fileToCheck.toPath());
}
} else {
throw new HarnessException(String.format("Could not find expected file: %s in directory %s withing the specified timeout", filename, directory));
}
} catch (Exception e) {
throw new HarnessException(String.format("Something went wrong: Could not find expected file: %s in directory %s.", filename, directory), e);
}
return fileExists;
}
}

View File

@ -1,99 +1,99 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.Until;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import java.util.List;
import java.util.Optional;
public class ElementsActions {
private static final int DEFAULT_TIMEOUT_FOR_WAIT = 30;
private static final Logger logger = LogManager.getLogger(ElementsActions.class);
private final SeleniumWebConnector connector;
private final String JS = "var evObj = document.createEvent('MouseEvents');"
+ "evObj.initMouseEvent(\"mouseover\",true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);"
+ "arguments[0].dispatchEvent(evObj);";
public ElementsActions(SeleniumWebConnector connector) {
this.connector = connector;
}
public void moveToElement(String path, Lookup lookup) {
Actions actions = new Actions(connector.getDriver());
actions.moveToElement(waitForLazyElement(path, lookup))
.build()
.perform();
}
public void moveToElementAndClick(String hoverElementPath, String clickElementPath, Lookup lookup) {
Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(e -> {
e.executeScript(JS, waitForLazyElement(hoverElementPath, lookup));
connector.clicks().click(() -> clickElementPath, lookup);
});
}
public String getText(String path, Lookup lookup) {
return waitForLazyElement(path, lookup).getText();
}
public boolean isElementVisible(String path, Lookup lookup) {
return isElementVisible(DEFAULT_TIMEOUT_FOR_WAIT, path, lookup);
}
public boolean isElementVisible(int timeout, String path, Lookup lookup) {
try {
waitForElement(timeout, path, lookup, Until.VISIBLE);
return true;
} catch (TimeoutException e) {
logger.warn("Element {} is not visible after {}s.", () -> path, () -> timeout);
return false;
}
}
public void dragAndDropElement(String source, String target, Lookup lookup) {
Actions actions = new Actions(connector.getDriver());
actions.dragAndDrop(waitForLazyElement(source, lookup), findElement(target, lookup))
.build()
.perform();
}
public void dragAndDropBy(String source, int xOffset, int yOffset, Lookup lookup) {
Actions actions = new Actions(connector.getDriver());
actions.dragAndDropBy(waitForLazyElement(source, lookup), xOffset, yOffset)
.build()
.perform();
}
private WebElement waitForLazyElement(String path, Lookup lookup) {
return connector.waits().waitForLazyElement(path, lookup);
}
private WebElement findElement(String path, Lookup lookup) {
return connector.getDriver()
.findElement(connector.resolveLookup(lookup).apply(path));
}
private void waitForElement(int timeout, String path, Lookup lookup, Until until) {
connector.waits().waitForElements(timeout, lookup, until, path);
}
public void scrollIntoView(String path, Lookup lookup) {
WebElement element = connector.getDriver().findElement(connector.resolveLookup(lookup).apply(path));
connector.scrolling().scrollIntoView(element);
}
public List<WebElement> findElements(String path, Lookup lookup) {
return connector.getDriver().findElements(connector.resolveLookup(lookup).apply(path));
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.Until;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import java.util.List;
import java.util.Optional;
public class ElementsActions {
private static final int DEFAULT_TIMEOUT_FOR_WAIT = 30;
private static final Logger logger = LogManager.getLogger(ElementsActions.class);
private final SeleniumWebConnector connector;
private final String JS = "var evObj = document.createEvent('MouseEvents');"
+ "evObj.initMouseEvent(\"mouseover\",true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);"
+ "arguments[0].dispatchEvent(evObj);";
public ElementsActions(SeleniumWebConnector connector) {
this.connector = connector;
}
public void moveToElement(String path, Lookup lookup) {
Actions actions = new Actions(connector.getDriver());
actions.moveToElement(waitForLazyElement(path, lookup))
.build()
.perform();
}
public void moveToElementAndClick(String hoverElementPath, String clickElementPath, Lookup lookup) {
Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(e -> {
e.executeScript(JS, waitForLazyElement(hoverElementPath, lookup));
connector.clicks().click(() -> clickElementPath, lookup);
});
}
public String getText(String path, Lookup lookup) {
return waitForLazyElement(path, lookup).getText();
}
public boolean isElementVisible(String path, Lookup lookup) {
return isElementVisible(DEFAULT_TIMEOUT_FOR_WAIT, path, lookup);
}
public boolean isElementVisible(int timeout, String path, Lookup lookup) {
try {
waitForElement(timeout, path, lookup, Until.VISIBLE);
return true;
} catch (TimeoutException e) {
logger.warn("Element {} is not visible after {}s.", () -> path, () -> timeout);
return false;
}
}
public void dragAndDropElement(String source, String target, Lookup lookup) {
Actions actions = new Actions(connector.getDriver());
actions.dragAndDrop(waitForLazyElement(source, lookup), findElement(target, lookup))
.build()
.perform();
}
public void dragAndDropBy(String source, int xOffset, int yOffset, Lookup lookup) {
Actions actions = new Actions(connector.getDriver());
actions.dragAndDropBy(waitForLazyElement(source, lookup), xOffset, yOffset)
.build()
.perform();
}
private WebElement waitForLazyElement(String path, Lookup lookup) {
return connector.waits().waitForLazyElement(path, lookup);
}
private WebElement findElement(String path, Lookup lookup) {
return connector.getDriver()
.findElement(connector.resolveLookup(lookup).apply(path));
}
private void waitForElement(int timeout, String path, Lookup lookup, Until until) {
connector.waits().waitForElements(timeout, lookup, until, path);
}
public void scrollIntoView(String path, Lookup lookup) {
WebElement element = connector.getDriver().findElement(connector.resolveLookup(lookup).apply(path));
connector.scrolling().scrollIntoView(element);
}
public List<WebElement> findElements(String path, Lookup lookup) {
return connector.getDriver().findElements(connector.resolveLookup(lookup).apply(path));
}
}

View File

@ -1,37 +1,37 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
import org.openqa.selenium.WebElement;
import java.util.List;
public class ElementsChecks {
private final SeleniumWebConnector connector;
public ElementsChecks(SeleniumWebConnector connector) {
this.connector = connector;
}
public boolean isElementPresent(String path, Lookup lookup) {
return !connector.getDriver().findElements(connector.resolveLookup(lookup).apply(path)).isEmpty();
}
public boolean isElementEnabled(String path, Lookup lookup) {
return connector.waits().waitForLazyElement(path, lookup).isEnabled();
}
public void isAtLeastOneElementPresent(String xpath, Lookup lookup) {
connector.waits()
.waitForLazyElement(xpath, lookup);
}
public void checkElementContent(String path, String content, Lookup lookup) {
List<WebElement> elements = connector.waits().waitForLazyElements(path, lookup);
elements.stream()
.map(WebElement::getText)
.filter(text -> text.contains(content))
.findFirst()
.orElseThrow(() -> new AssertionError(String.format("Cannot find element that contains text %s", content)));
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
import org.openqa.selenium.WebElement;
import java.util.List;
public class ElementsChecks {
private final SeleniumWebConnector connector;
public ElementsChecks(SeleniumWebConnector connector) {
this.connector = connector;
}
public boolean isElementPresent(String path, Lookup lookup) {
return !connector.getDriver().findElements(connector.resolveLookup(lookup).apply(path)).isEmpty();
}
public boolean isElementEnabled(String path, Lookup lookup) {
return connector.waits().waitForLazyElement(path, lookup).isEnabled();
}
public void isAtLeastOneElementPresent(String xpath, Lookup lookup) {
connector.waits()
.waitForLazyElement(xpath, lookup);
}
public void checkElementContent(String path, String content, Lookup lookup) {
List<WebElement> elements = connector.waits().waitForLazyElements(path, lookup);
elements.stream()
.map(WebElement::getText)
.filter(text -> text.contains(content))
.findFirst()
.orElseThrow(() -> new AssertionError(String.format("Cannot find element that contains text %s", content)));
}
}

View File

@ -1,19 +1,19 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
public class Frames {
private final SeleniumWebConnector connector;
public Frames(SeleniumWebConnector connector) {
this.connector = connector;
}
public void switchToFrame(String frame, Lookup lookup) {
connector
.getDriver()
.switchTo().frame(connector.waits().waitForLazyElement(frame, lookup));
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
public class Frames {
private final SeleniumWebConnector connector;
public Frames(SeleniumWebConnector connector) {
this.connector = connector;
}
public void switchToFrame(String frame, Lookup lookup) {
connector
.getDriver()
.switchTo().frame(connector.waits().waitForLazyElement(frame, lookup));
}
}

View File

@ -1,40 +1,40 @@
package cz.moneta.test.harness.connectors.web;
public class Navigations {
private final SeleniumWebConnector connector;
public Navigations(SeleniumWebConnector connector) {
this.connector = connector;
}
public void switchToDefaultContent() {
connector.getDriver()
.switchTo()
.defaultContent();
}
public void navigateTo(String url) {
connector.getDriver()
.navigate()
.to(url);
}
public void navigateRefresh() {
connector.getDriver()
.navigate()
.refresh();
}
public void navigateBack() {
connector.getDriver()
.navigate()
.back();
}
public void navigateForward() {
connector.getDriver()
.navigate()
.forward();
}
}
package cz.moneta.test.harness.connectors.web;
public class Navigations {
private final SeleniumWebConnector connector;
public Navigations(SeleniumWebConnector connector) {
this.connector = connector;
}
public void switchToDefaultContent() {
connector.getDriver()
.switchTo()
.defaultContent();
}
public void navigateTo(String url) {
connector.getDriver()
.navigate()
.to(url);
}
public void navigateRefresh() {
connector.getDriver()
.navigate()
.refresh();
}
public void navigateBack() {
connector.getDriver()
.navigate()
.back();
}
public void navigateForward() {
connector.getDriver()
.navigate()
.forward();
}
}

View File

@ -1,24 +1,24 @@
package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.JavascriptExecutor;
import java.util.Optional;
public class Scripts {
private final SeleniumWebConnector connector;
public Scripts(SeleniumWebConnector connector) {
this.connector = connector;
}
public Object executeScript(String script, Object[] args) {
JavascriptExecutor executor = Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.orElseThrow(() -> new IllegalStateException("This driver does not support JavaScript!"));
return args == null
? executor.executeScript(script)
: executor.executeScript(script, args);
}
}
package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.JavascriptExecutor;
import java.util.Optional;
public class Scripts {
private final SeleniumWebConnector connector;
public Scripts(SeleniumWebConnector connector) {
this.connector = connector;
}
public Object executeScript(String script, Object[] args) {
JavascriptExecutor executor = Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.orElseThrow(() -> new IllegalStateException("This driver does not support JavaScript!"));
return args == null
? executor.executeScript(script)
: executor.executeScript(script, args);
}
}

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.WebElement;
public class Scrolling {
private final SeleniumWebConnector connector;
public Scrolling(SeleniumWebConnector connector) {
this.connector = connector;
}
public void scrollIntoView(WebElement element) {
connector.scripts().executeScript("arguments[0].scrollIntoView(true);", new Object[]{element});
}
}
package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.WebElement;
public class Scrolling {
private final SeleniumWebConnector connector;
public Scrolling(SeleniumWebConnector connector) {
this.connector = connector;
}
public void scrollIntoView(WebElement element) {
connector.scripts().executeScript("arguments[0].scrollIntoView(true);", new Object[]{element});
}
}

View File

@ -1,80 +1,80 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Select;
import java.util.List;
import java.util.stream.Collectors;
public class Selects {
private final SeleniumWebConnector connector;
public Selects(SeleniumWebConnector connector) {
this.connector = connector;
}
public void selectByVisibleText(String path, String text, Lookup lookup) {
WebElement element = connector.waits()
.waitForLazyElement(path, lookup);
connector.scrolling().scrollIntoView(element);
new Select(element).selectByVisibleText(text);
}
public void selectByValue(String path, String value, Lookup lookup) {
WebElement element = connector.waits()
.waitForLazyElement(path, lookup);
connector.scrolling().scrollIntoView(element);
connector.elementsActions().moveToElement(path, lookup);
new Select(element).selectByValue(value);
}
public void selectByOptionOrder(String path, int optionOrder, Lookup lookup) {
WebElement element = connector.waits()
.waitForLazyElement(path, lookup);
connector.scrolling().scrollIntoView(element);
new Select(element).selectByIndex(--optionOrder);
}
/**
* Select option by text part of option value
* <p>
* In some cases is necessary to select option only by part of value in option. This method accepts path to element
* lookup strategy value and list of text parts of options.
* </p>
* <p>
* In this method is created in xpath for select one value. Used strategy: create xpath with contains separated by
* OR.
* </p>
* <p>
* Example:
* <pre>
* From ["ValueA", "ValueB"] will create xpath:
* "//option[contains(text(),'ValueA') or contains(text(),'ValueB')]"
* </pre>
* </p>
*
* @param path - path to select element
* @param lookup - lookup strategy for select element
* @param textOptionsParts - list of option text parts to look for
*/
public void selectByContainingTexts(String path, Lookup lookup, List<String> textOptionsParts) {
List<String> optionXpaths = textOptionsParts.stream()
.map(text -> String.format("contains(text(),'%s')", text))
.collect(Collectors.toList());
String joinedContains = String.join(" or ", optionXpaths);
String optionXpath = String.format("//option[%s]", joinedContains);
Waits waits = connector.waits();
waits.waitForLazyElement(path, lookup)
.findElements(waits.byLocator(Lookup.XPATH, optionXpath))
.get(0)
.click();
}
public String getFirstSelectedOptionText(String path, Lookup lookup) {
Select select = new Select(connector.waits().waitForLazyElement(path, lookup));
return select.getFirstSelectedOption().getText();
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Select;
import java.util.List;
import java.util.stream.Collectors;
public class Selects {
private final SeleniumWebConnector connector;
public Selects(SeleniumWebConnector connector) {
this.connector = connector;
}
public void selectByVisibleText(String path, String text, Lookup lookup) {
WebElement element = connector.waits()
.waitForLazyElement(path, lookup);
connector.scrolling().scrollIntoView(element);
new Select(element).selectByVisibleText(text);
}
public void selectByValue(String path, String value, Lookup lookup) {
WebElement element = connector.waits()
.waitForLazyElement(path, lookup);
connector.scrolling().scrollIntoView(element);
connector.elementsActions().moveToElement(path, lookup);
new Select(element).selectByValue(value);
}
public void selectByOptionOrder(String path, int optionOrder, Lookup lookup) {
WebElement element = connector.waits()
.waitForLazyElement(path, lookup);
connector.scrolling().scrollIntoView(element);
new Select(element).selectByIndex(--optionOrder);
}
/**
* Select option by text part of option value
* <p>
* In some cases is necessary to select option only by part of value in option. This method accepts path to element
* lookup strategy value and list of text parts of options.
* </p>
* <p>
* In this method is created in xpath for select one value. Used strategy: create xpath with contains separated by
* OR.
* </p>
* <p>
* Example:
* <pre>
* From ["ValueA", "ValueB"] will create xpath:
* "//option[contains(text(),'ValueA') or contains(text(),'ValueB')]"
* </pre>
* </p>
*
* @param path - path to select element
* @param lookup - lookup strategy for select element
* @param textOptionsParts - list of option text parts to look for
*/
public void selectByContainingTexts(String path, Lookup lookup, List<String> textOptionsParts) {
List<String> optionXpaths = textOptionsParts.stream()
.map(text -> String.format("contains(text(),'%s')", text))
.collect(Collectors.toList());
String joinedContains = String.join(" or ", optionXpaths);
String optionXpath = String.format("//option[%s]", joinedContains);
Waits waits = connector.waits();
waits.waitForLazyElement(path, lookup)
.findElements(waits.byLocator(Lookup.XPATH, optionXpath))
.get(0)
.click();
}
public String getFirstSelectedOptionText(String path, Lookup lookup) {
Select select = new Select(connector.waits().waitForLazyElement(path, lookup));
return select.getFirstSelectedOption().getText();
}
}

View File

@ -1,92 +1,92 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_GRID_PLATFORM;
public class SeleniumChromeConnector extends SeleniumWebConnector {
public SeleniumChromeConnector(Function<String, By> defaultLookup, StoreAccessor store, String... extraOptions) {
super(defaultLookup, store, extraOptions);
}
@Override
protected Pair<WebDriver, Boolean> getLocalWebDriver() {
setupDriver();
return Optional.ofNullable(store.getConfig("selenium.chromebinary.path"))
.map(bin -> {
ChromeOptions chromeOptions = getChromeOptions();
chromeOptions.setBinary(bin);
return chromeOptions;
})
.map(o -> Pair.<WebDriver, Boolean>of(new ChromeDriver(o), false))
.orElse(null);
}
private void setupDriver() {
Optional.ofNullable(store.getConfig("selenium.chrome.webdriver.path"))
.map(path -> {
System.setProperty("webdriver.chrome.driver", path);
return path;
})
.orElseThrow(() -> new AssertionError("Please provide path to the Chrome driver using selenium.chrome.webdriver.path configuration key or use a selenium grid"));
}
private ChromeOptions getChromeOptions() {
ChromeOptions chromeOptions = new ChromeOptions();
Optional.ofNullable(store.getConfig("selenium.webdriver.chrome.options"))
.map(o -> o.split("\\s+"))
.ifPresent(chromeOptions::addArguments);
chromeOptions.addArguments(extraOptions);
this.driverUtils().addDefaultDesiredCapabilities(chromeOptions);
this.driverUtils().addAdditionalDesiredCapabilities(chromeOptions, "selenium.chrome.capabilities");
// Set up Selenium Grid session name
chromeOptions.setCapability("se:name", store.get(HarnessConfigConstants.TEST_UNIQUE_ID).toString());
// Choose which Selenium Grid node will be used
Optional.ofNullable(store.getConfig(SELENIUM_GRID_PLATFORM))
.ifPresent(platform -> chromeOptions.setPlatformName(platform));
// Overrides default download directory in Chrome profile preferences, http://chromedriver.chromium.org/capabilities
// By default headless mode does not download any files unless download directory is set.
Optional.ofNullable(store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY))
.ifPresent(experimentalOptions -> {
Map<String, Object> prefs = new HashMap<String, Object>();
prefs.put("download.default_directory", store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY));
chromeOptions.setExperimentalOption("prefs", prefs);
});
return chromeOptions;
}
@Override
protected Capabilities getDesiredRemoteCapabilities() {
return new DesiredCapabilities(getChromeOptions());
}
@Override
public void downloadFileViaIePrompt(int waitInSeconds) {
throw new HarnessException("This method is not implemented on Chrome. Use capabilities settings instead.");
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_GRID_PLATFORM;
public class SeleniumChromeConnector extends SeleniumWebConnector {
public SeleniumChromeConnector(Function<String, By> defaultLookup, StoreAccessor store, String... extraOptions) {
super(defaultLookup, store, extraOptions);
}
@Override
protected Pair<WebDriver, Boolean> getLocalWebDriver() {
setupDriver();
return Optional.ofNullable(store.getConfig("selenium.chromebinary.path"))
.map(bin -> {
ChromeOptions chromeOptions = getChromeOptions();
chromeOptions.setBinary(bin);
return chromeOptions;
})
.map(o -> Pair.<WebDriver, Boolean>of(new ChromeDriver(o), false))
.orElse(null);
}
private void setupDriver() {
Optional.ofNullable(store.getConfig("selenium.chrome.webdriver.path"))
.map(path -> {
System.setProperty("webdriver.chrome.driver", path);
return path;
})
.orElseThrow(() -> new AssertionError("Please provide path to the Chrome driver using selenium.chrome.webdriver.path configuration key or use a selenium grid"));
}
private ChromeOptions getChromeOptions() {
ChromeOptions chromeOptions = new ChromeOptions();
Optional.ofNullable(store.getConfig("selenium.webdriver.chrome.options"))
.map(o -> o.split("\\s+"))
.ifPresent(chromeOptions::addArguments);
chromeOptions.addArguments(extraOptions);
this.driverUtils().addDefaultDesiredCapabilities(chromeOptions);
this.driverUtils().addAdditionalDesiredCapabilities(chromeOptions, "selenium.chrome.capabilities");
// Set up Selenium Grid session name
chromeOptions.setCapability("se:name", store.get(HarnessConfigConstants.TEST_UNIQUE_ID).toString());
// Choose which Selenium Grid node will be used
Optional.ofNullable(store.getConfig(SELENIUM_GRID_PLATFORM))
.ifPresent(platform -> chromeOptions.setPlatformName(platform));
// Overrides default download directory in Chrome profile preferences, http://chromedriver.chromium.org/capabilities
// By default headless mode does not download any files unless download directory is set.
Optional.ofNullable(store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY))
.ifPresent(experimentalOptions -> {
Map<String, Object> prefs = new HashMap<String, Object>();
prefs.put("download.default_directory", store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY));
chromeOptions.setExperimentalOption("prefs", prefs);
});
return chromeOptions;
}
@Override
protected Capabilities getDesiredRemoteCapabilities() {
return new DesiredCapabilities(getChromeOptions());
}
@Override
public void downloadFileViaIePrompt(int waitInSeconds) {
throw new HarnessException("This method is not implemented on Chrome. Use capabilities settings instead.");
}
}

View File

@ -1,91 +1,91 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_GRID_PLATFORM;
public class SeleniumEdgeConnector extends SeleniumWebConnector {
public SeleniumEdgeConnector(Function<String, By> defaultLookup, StoreAccessor store) {
super(defaultLookup, store);
}
@Override
protected Pair<WebDriver, Boolean> getLocalWebDriver() {
setupDriver();
EdgeDriver driver = new EdgeDriver(getEdgeOptions());
driver.manage()
.window()
.maximize();
return Pair.of(driver, false);
}
private void setupDriver() {
Optional.ofNullable(store.getConfig("selenium.edge.webdriver.path"))
.map(path -> {
System.setProperty("webdriver.edge.driver", path);
return path;
})
.orElseThrow(() -> new AssertionError("Please provide path to the Edge driver using selenium.edge.webdriver.path configuration key or use a selenium grid"));
}
private EdgeOptions getEdgeOptions() {
EdgeOptions edgeOptions = new EdgeOptions();
this.driverUtils().addDefaultDesiredCapabilities(edgeOptions);
Optional.ofNullable(store.getConfig("selenium.webdriver.edge.options"))
.map(o -> o.split("\\s+"))
.ifPresent(edgeOptions::addArguments);
edgeOptions.addArguments(extraOptions);
this.driverUtils().addAdditionalDesiredCapabilities(edgeOptions, "selenium.edge.capabilities");
// Set up Selenium Grid session name
edgeOptions.setCapability("se:name", store.get(HarnessConfigConstants.TEST_UNIQUE_ID).toString());
// Choose which Selenium Grid node will be used
Optional.ofNullable(store.getConfig(SELENIUM_GRID_PLATFORM))
.ifPresent(platform -> edgeOptions.setPlatformName(platform));
Map<String, Object> prefs = new HashMap<String, Object>();
// Allow clipboard usage
prefs.put("profile.default_content_setting_values.clipboard", 1);
// Overrides default download directory in Edge profile preferences
Optional.ofNullable(store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY))
.ifPresent(experimentalOptions -> {
prefs.put("download.default_directory", store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY));
});
edgeOptions.setExperimentalOption("prefs", prefs);
return edgeOptions;
}
@Override
protected Capabilities getDesiredRemoteCapabilities() {
return new DesiredCapabilities(getEdgeOptions());
}
@Override
public void downloadFileViaIePrompt(int waitInSeconds) {
throw new HarnessException("This method is not implemented on Chrome. Use capabilities settings instead.");
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_GRID_PLATFORM;
public class SeleniumEdgeConnector extends SeleniumWebConnector {
public SeleniumEdgeConnector(Function<String, By> defaultLookup, StoreAccessor store) {
super(defaultLookup, store);
}
@Override
protected Pair<WebDriver, Boolean> getLocalWebDriver() {
setupDriver();
EdgeDriver driver = new EdgeDriver(getEdgeOptions());
driver.manage()
.window()
.maximize();
return Pair.of(driver, false);
}
private void setupDriver() {
Optional.ofNullable(store.getConfig("selenium.edge.webdriver.path"))
.map(path -> {
System.setProperty("webdriver.edge.driver", path);
return path;
})
.orElseThrow(() -> new AssertionError("Please provide path to the Edge driver using selenium.edge.webdriver.path configuration key or use a selenium grid"));
}
private EdgeOptions getEdgeOptions() {
EdgeOptions edgeOptions = new EdgeOptions();
this.driverUtils().addDefaultDesiredCapabilities(edgeOptions);
Optional.ofNullable(store.getConfig("selenium.webdriver.edge.options"))
.map(o -> o.split("\\s+"))
.ifPresent(edgeOptions::addArguments);
edgeOptions.addArguments(extraOptions);
this.driverUtils().addAdditionalDesiredCapabilities(edgeOptions, "selenium.edge.capabilities");
// Set up Selenium Grid session name
edgeOptions.setCapability("se:name", store.get(HarnessConfigConstants.TEST_UNIQUE_ID).toString());
// Choose which Selenium Grid node will be used
Optional.ofNullable(store.getConfig(SELENIUM_GRID_PLATFORM))
.ifPresent(platform -> edgeOptions.setPlatformName(platform));
Map<String, Object> prefs = new HashMap<String, Object>();
// Allow clipboard usage
prefs.put("profile.default_content_setting_values.clipboard", 1);
// Overrides default download directory in Edge profile preferences
Optional.ofNullable(store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY))
.ifPresent(experimentalOptions -> {
prefs.put("download.default_directory", store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY));
});
edgeOptions.setExperimentalOption("prefs", prefs);
return edgeOptions;
}
@Override
protected Capabilities getDesiredRemoteCapabilities() {
return new DesiredCapabilities(getEdgeOptions());
}
@Override
public void downloadFileViaIePrompt(int waitInSeconds) {
throw new HarnessException("This method is not implemented on Chrome. Use capabilities settings instead.");
}
}

View File

@ -1,185 +1,185 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.web.Lookup;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public abstract class SeleniumWebConnector implements Connector {
private final WebDriver webDriver;
private final boolean isRemoteDriver;
protected final Function<String, By> defaultLookup;
protected final StoreAccessor store;
protected final String[] extraOptions;
private String nodeHost;
public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store, String[] extraOptions) {
this.extraOptions = extraOptions;
this.store = store;
this.defaultLookup = defaultLookup;
Pair<WebDriver, Boolean> driverInfo = getPreferredWebDriver();
isRemoteDriver = driverInfo.getRight();
WebDriver driver = driverInfo.getLeft();
if (driver == null) {
throw new HarnessException("We were not able to initialize webDriver. Please check your configs, logs and talk to your admin");
}
//TODO fix this - it slows down test execution but some tests are build with this implicit wait
driver.manage().timeouts().implicitlyWait(2L, TimeUnit.SECONDS);
driver.manage().timeouts().pageLoadTimeout(90L, TimeUnit.SECONDS);
this.webDriver = driver;
}
public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store) {
this(defaultLookup, store, new String[]{});
}
public Clicks clicks() {
return new Clicks(this);
}
public Waits waits() {
return new Waits(this);
}
public Alerts alerts() {
return new Alerts(this);
}
public Types types() {
return new Types(this);
}
public Selects selects() {
return new Selects(this);
}
public SendKeys sendKeys() {
return new SendKeys(this);
}
public Frames frames() {
return new Frames(this);
}
public Windows windows() {
return new Windows(this);
}
public Navigations navigations() {
return new Navigations(this);
}
public ElementsChecks elementsChecks() {
return new ElementsChecks(this);
}
public Cookies cookies() {
return new Cookies(this);
}
public Scripts scripts() {
return new Scripts(this);
}
public ElementsActions elementsActions() {
return new ElementsActions(this);
}
public Scrolling scrolling() {return new Scrolling(this); }
public DriverUtils driverUtils() {
return new DriverUtils(this);
}
public StoreAccessor getStore() {
return store;
}
public boolean isRemoteDriver() {
return isRemoteDriver;
}
public abstract void downloadFileViaIePrompt(int waitInSeconds);
/**
* returns a pair of web driver and whether it is a remote instance (true = remote, false = local driver)
*/
private Pair<WebDriver, Boolean> getPreferredWebDriver() {
return Stream.<Supplier<Pair<WebDriver, Boolean>>>of(this::getDedicatedWebDriver, this::getSharedWebDriver, this::getLocalWebDriver)
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> new IllegalStateException("No suitable web driver could be initialized."));
}
private Pair<WebDriver, Boolean> getDedicatedWebDriver() {
return Optional.ofNullable(store.getConfig("selenium.grid.dedicated.url"))
.map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true))
.orElse(null);
}
private Pair<WebDriver, Boolean> getSharedWebDriver() {
return Optional.ofNullable(store.getConfig("selenium.grid.shared.url"))
.map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true))
.orElse(null);
}
public String getAttribute(String path, Lookup lookup, String attributeName) {
return waits().waitForLazyElement(path, lookup)
.getAttribute(attributeName );
}
protected abstract Pair<WebDriver, Boolean> getLocalWebDriver();
protected abstract Capabilities getDesiredRemoteCapabilities();
public WebDriver getDriver() {
return webDriver;
}
@Override
public void close() {
webDriver.quit();
}
public String getCurrentUrl() {
return webDriver.getCurrentUrl();
}
protected Function<String, By> resolveLookup(Lookup lookup) {
switch (lookup) {
case XPATH:
return By::xpath;
case ID:
return By::id;
case CLASSNAME:
return By::className;
case NAME:
return By::name;
default:
return defaultLookup;
}
}
public String getNodeHost() {
return nodeHost;
}
public void setNodeHost(String nodeHost) {
this.nodeHost = nodeHost;
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.web.Lookup;
import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public abstract class SeleniumWebConnector implements Connector {
private final WebDriver webDriver;
private final boolean isRemoteDriver;
protected final Function<String, By> defaultLookup;
protected final StoreAccessor store;
protected final String[] extraOptions;
private String nodeHost;
public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store, String[] extraOptions) {
this.extraOptions = extraOptions;
this.store = store;
this.defaultLookup = defaultLookup;
Pair<WebDriver, Boolean> driverInfo = getPreferredWebDriver();
isRemoteDriver = driverInfo.getRight();
WebDriver driver = driverInfo.getLeft();
if (driver == null) {
throw new HarnessException("We were not able to initialize webDriver. Please check your configs, logs and talk to your admin");
}
//TODO fix this - it slows down test execution but some tests are build with this implicit wait
driver.manage().timeouts().implicitlyWait(2L, TimeUnit.SECONDS);
driver.manage().timeouts().pageLoadTimeout(90L, TimeUnit.SECONDS);
this.webDriver = driver;
}
public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store) {
this(defaultLookup, store, new String[]{});
}
public Clicks clicks() {
return new Clicks(this);
}
public Waits waits() {
return new Waits(this);
}
public Alerts alerts() {
return new Alerts(this);
}
public Types types() {
return new Types(this);
}
public Selects selects() {
return new Selects(this);
}
public SendKeys sendKeys() {
return new SendKeys(this);
}
public Frames frames() {
return new Frames(this);
}
public Windows windows() {
return new Windows(this);
}
public Navigations navigations() {
return new Navigations(this);
}
public ElementsChecks elementsChecks() {
return new ElementsChecks(this);
}
public Cookies cookies() {
return new Cookies(this);
}
public Scripts scripts() {
return new Scripts(this);
}
public ElementsActions elementsActions() {
return new ElementsActions(this);
}
public Scrolling scrolling() {return new Scrolling(this); }
public DriverUtils driverUtils() {
return new DriverUtils(this);
}
public StoreAccessor getStore() {
return store;
}
public boolean isRemoteDriver() {
return isRemoteDriver;
}
public abstract void downloadFileViaIePrompt(int waitInSeconds);
/**
* returns a pair of web driver and whether it is a remote instance (true = remote, false = local driver)
*/
private Pair<WebDriver, Boolean> getPreferredWebDriver() {
return Stream.<Supplier<Pair<WebDriver, Boolean>>>of(this::getDedicatedWebDriver, this::getSharedWebDriver, this::getLocalWebDriver)
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> new IllegalStateException("No suitable web driver could be initialized."));
}
private Pair<WebDriver, Boolean> getDedicatedWebDriver() {
return Optional.ofNullable(store.getConfig("selenium.grid.dedicated.url"))
.map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true))
.orElse(null);
}
private Pair<WebDriver, Boolean> getSharedWebDriver() {
return Optional.ofNullable(store.getConfig("selenium.grid.shared.url"))
.map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true))
.orElse(null);
}
public String getAttribute(String path, Lookup lookup, String attributeName) {
return waits().waitForLazyElement(path, lookup)
.getAttribute(attributeName );
}
protected abstract Pair<WebDriver, Boolean> getLocalWebDriver();
protected abstract Capabilities getDesiredRemoteCapabilities();
public WebDriver getDriver() {
return webDriver;
}
@Override
public void close() {
webDriver.quit();
}
public String getCurrentUrl() {
return webDriver.getCurrentUrl();
}
protected Function<String, By> resolveLookup(Lookup lookup) {
switch (lookup) {
case XPATH:
return By::xpath;
case ID:
return By::id;
case CLASSNAME:
return By::className;
case NAME:
return By::name;
default:
return defaultLookup;
}
}
public String getNodeHost() {
return nodeHost;
}
public void setNodeHost(String nodeHost) {
this.nodeHost = nodeHost;
}
}

View File

@ -1,35 +1,35 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Key;
import org.openqa.selenium.Keys;
import org.openqa.selenium.interactions.Actions;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class SendKeys {
private final SeleniumWebConnector connector;
public SendKeys(SeleniumWebConnector connector) {
this.connector = connector;
}
public void sendKeysOneAtATime(Key[] keys) {
Actions actions = new Actions(connector.getDriver());
Arrays.stream(keys)
.forEach(k -> {
actions.sendKeys(k).build().perform();
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
//
}
});
}
public void sendKeysAsChord(Key[] keys) {
Actions actions = new Actions(connector.getDriver());
actions.sendKeys(Keys.chord(keys)).build().perform();
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Key;
import org.openqa.selenium.Keys;
import org.openqa.selenium.interactions.Actions;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class SendKeys {
private final SeleniumWebConnector connector;
public SendKeys(SeleniumWebConnector connector) {
this.connector = connector;
}
public void sendKeysOneAtATime(Key[] keys) {
Actions actions = new Actions(connector.getDriver());
Arrays.stream(keys)
.forEach(k -> {
actions.sendKeys(k).build().perform();
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
//
}
});
}
public void sendKeysAsChord(Key[] keys) {
Actions actions = new Actions(connector.getDriver());
actions.sendKeys(Keys.chord(keys)).build().perform();
}
}

View File

@ -1,61 +1,61 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.TextContainer;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import java.util.Optional;
public class Types {
private final SeleniumWebConnector connector;
public Types(SeleniumWebConnector connector) {
this.connector = connector;
}
public void type(TextContainer input, String text, boolean clear, Lookup lookup) {
if (clear) {
clearInput(input, lookup);
}
connector.waits()
.waitForLazyElement(input.getPath(), lookup)
.sendKeys(text);
}
public void jsType(TextContainer input, String text, boolean clear, Lookup lookup) {
if (clear) {
clearInput(input, lookup);
}
Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(e -> {
WebElement element = connector.waits().waitForLazyElement(input.getPath(), lookup);
if ("textarea".equals(element.getTagName())) {
e.executeScript("arguments[0].value=arguments[1];", element, text);
} else {
e.executeScript("arguments[0].setAttribute('value', arguments[1]);", element, text);
}
});
}
private void clearInput(TextContainer input, Lookup lookup) {
connector.waits()
.waitForLazyElement(input.getPath(), lookup)
.sendKeys(Keys.chord(Keys.CONTROL, "a"), Keys.BACK_SPACE);
}
public void typeWithControlDown(String input, String keys, Lookup lookup) {
new Actions(connector.getDriver())
.click(connector.waits().waitForLazyElement(input, lookup))
.keyDown(Keys.CONTROL)
.sendKeys(keys)
.keyUp(Keys.CONTROL)
.perform();
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.TextContainer;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import java.util.Optional;
public class Types {
private final SeleniumWebConnector connector;
public Types(SeleniumWebConnector connector) {
this.connector = connector;
}
public void type(TextContainer input, String text, boolean clear, Lookup lookup) {
if (clear) {
clearInput(input, lookup);
}
connector.waits()
.waitForLazyElement(input.getPath(), lookup)
.sendKeys(text);
}
public void jsType(TextContainer input, String text, boolean clear, Lookup lookup) {
if (clear) {
clearInput(input, lookup);
}
Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast)
.ifPresent(e -> {
WebElement element = connector.waits().waitForLazyElement(input.getPath(), lookup);
if ("textarea".equals(element.getTagName())) {
e.executeScript("arguments[0].value=arguments[1];", element, text);
} else {
e.executeScript("arguments[0].setAttribute('value', arguments[1]);", element, text);
}
});
}
private void clearInput(TextContainer input, Lookup lookup) {
connector.waits()
.waitForLazyElement(input.getPath(), lookup)
.sendKeys(Keys.chord(Keys.CONTROL, "a"), Keys.BACK_SPACE);
}
public void typeWithControlDown(String input, String keys, Lookup lookup) {
new Actions(connector.getDriver())
.click(connector.waits().waitForLazyElement(input, lookup))
.keyDown(Keys.CONTROL)
.sendKeys(keys)
.keyUp(Keys.CONTROL)
.perform();
}
}

View File

@ -1,184 +1,184 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.Until;
import cz.moneta.test.harness.utils.expectedconditions.BooleanExpectedConditions;
import org.apache.commons.lang3.ArrayUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
public class Waits {
private final SeleniumWebConnector connector;
private static final int LAZY_ELEMENT_RENDER_TIMEOUT = 5;
public Waits(SeleniumWebConnector connector) {
this.connector = connector;
}
public void waitForElements(int timeoutSeconds, Lookup lookup, Until until, String... elementsToCheck) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
wait.ignoring(WebDriverException.class);
Arrays.stream(elementsToCheck)
.forEach(path -> wait.until(resolveUntil(until).apply(byLocator(lookup, path))));
}
public void waitForAtLeastOneElement(int timeoutSeconds, Lookup lookup, String... elementsToCheck) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
ExpectedCondition[] expectedConditions = Arrays.stream(elementsToCheck)
.map(path ->
ExpectedConditions.visibilityOfElementLocated(byLocator(lookup, path)))
.toArray(ExpectedCondition[]::new);
wait.until(ExpectedConditions.or(expectedConditions));
}
WebElement waitForLazyElement(String path, Lookup lookup) {
doWaitForLazyElement(path, lookup);
return connector.getDriver()
.findElement(byLocator(lookup, path));
}
List<WebElement> waitForLazyElements(String path, Lookup lookup) {
doWaitForLazyElement(path, lookup);
return connector.getDriver()
.findElements(byLocator(lookup, path));
}
private void doWaitForLazyElement(String path, Lookup lookup) {
new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(LAZY_ELEMENT_RENDER_TIMEOUT))
.pollingEvery(Duration.ofMillis(500))
.ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class)
.withMessage("Element/s not found within " + LAZY_ELEMENT_RENDER_TIMEOUT
+ " seconds timeout. If this error occurs, you might want to consider using explicit @Wait annotation. " + path)
.until(driver -> !driver.findElements(byLocator(lookup, path)).isEmpty());
}
public void waitForElementAndRefresh(String waitElementPath, String refreshElementPath, Lookup waitElementLookup,
Lookup refreshElementLookup, int timeoutSeconds, int pollingEverySeconds) {
Runnable refreshFunction = () -> connector.clicks().click(() -> refreshElementPath, refreshElementLookup);
waitForElementAndRefresh(waitElementPath, waitElementLookup, refreshFunction, timeoutSeconds,
pollingEverySeconds, true);
// waitForLazyElement(refreshElementPath, refreshElementLookup).click();
}
public void waitForElementAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction,
int timeoutSeconds, int pollingEverySeconds, boolean shouldBeWaitElementVisible) {
new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(timeoutSeconds))
.pollingEvery(Duration.ofSeconds(pollingEverySeconds))
.ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class)
.ignoring(TimeoutException.class)
.withMessage("Element/s not present within " + timeoutSeconds + " seconds timeout. Wait and refreshed " +
"every: " + pollingEverySeconds + " seconds.\n For element: " + waitElementPath)
.until(driver -> {
if (shouldBeWaitElementVisible == true) {
return checkIfElementPresentAndRefresh(waitElementPath, waitElementLookup, refreshFunction, driver);
} else {
return checkIfElementDisappearAndRefresh(waitElementPath, waitElementLookup, refreshFunction, driver);
}
});
}
private WebElement checkIfElementPresentAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction, WebDriver driver) {
Optional.of(driver.findElements(byLocator(waitElementLookup, waitElementPath)))
.filter(Collection::isEmpty)
.ifPresent(col -> refreshFunction.run());
return driver.findElement(byLocator(waitElementLookup, waitElementPath));
}
private boolean checkIfElementDisappearAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction, WebDriver driver) {
Optional.of(driver.findElements(byLocator(waitElementLookup, waitElementPath)))
.ifPresent(col -> refreshFunction.run());
return driver.findElements(byLocator(waitElementLookup, waitElementPath)).size() == 0;
}
public void waitOrFailOnErrorElement(int timeoutSeconds, String pathForWaiting, String pathToFail, Lookup lookup) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
wait.withTimeout(Duration.ofSeconds(timeoutSeconds))
.pollingEvery(Duration.ofSeconds(3))
.withMessage("Element " + pathForWaiting + " or " + pathToFail + " isn't present after " + timeoutSeconds + " seconds")
.until(ExpectedConditions.or(
visibilityOfElementLocated(lookup, pathForWaiting),
visibilityOfElementLocated(lookup, pathToFail)));
if (connector.elementsChecks().isElementPresent(pathToFail, lookup)) {
throw new HarnessException(pathToFail + " is present instead of " + pathForWaiting);
}
}
/**
* Method for wait if all elements passed as parameter in defined state (Visible, Gone, Present in DOM) and there is not
* visible fail element. Fail element is for example error screen.
* <p>
* Wait is implemented as fluent wait with 500ms polling and all parameters are transformed into expected conditions.
* These conditions are used in fluent wait.
* </p>
* <p>
* In case of fail element visible is thrown FailElementDisplayedException.
* </p>
*
* @param timeoutSeconds - wait timeout
* @param lookup - define locator strategy
* @param until - state to check - Visible, Gone, Present in DOM
* @param xpathToFail - xpath for fail element/error screen
* @param elementsToCheck - elements to be wait for
*/
public void waitOrFailOnErrorXpath(int timeoutSeconds, Lookup lookup, Until until, String xpathToFail, String... elementsToCheck) {
ExpectedCondition[] conditionsToCheck = Arrays.stream(elementsToCheck)
.map(elementToCheck -> resolveUntil(until).apply(byLocator(lookup, elementToCheck)))
.toArray(ExpectedCondition[]::new);
ExpectedCondition[] failElementNotDisplayed = new ExpectedCondition[]{BooleanExpectedConditions.failElementNotDisplayed(byLocator(Lookup.XPATH, xpathToFail))};
ExpectedCondition[] conditionsToEvaluate = ArrayUtils.addAll(failElementNotDisplayed, conditionsToCheck);
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
wait.withTimeout(Duration.ofSeconds(timeoutSeconds))
.pollingEvery(Duration.ofMillis(500L))
.withMessage(getErrorElementsMessage(timeoutSeconds, elementsToCheck))
.until(ExpectedConditions.and(conditionsToEvaluate));
}
private String getErrorElementsMessage(int timeoutSeconds, String... elements) {
String elementsInString = String.join(",", elements);
return "Some of listed elements isn't present after " + timeoutSeconds + " seconds. Elements: " + elementsInString;
}
public By byLocator(Lookup lookup, String path) {
return connector.resolveLookup(lookup).apply(path);
}
private ExpectedCondition<WebElement> visibilityOfElementLocated(Lookup lookup, String path) {
return ExpectedConditions.visibilityOfElementLocated(byLocator(lookup, path));
}
private Function<By, ExpectedCondition<?>> resolveUntil(Until until) {
switch (until) {
case VISIBLE:
return ExpectedConditions::visibilityOfElementLocated;
case PRESENT_IN_DOM:
return ExpectedConditions::presenceOfElementLocated;
case GONE:
return ExpectedConditions::invisibilityOfElementLocated;
case CLICKABLE:
return ExpectedConditions::elementToBeClickable;
default:
return ExpectedConditions::presenceOfElementLocated;
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.Until;
import cz.moneta.test.harness.utils.expectedconditions.BooleanExpectedConditions;
import org.apache.commons.lang3.ArrayUtils;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
public class Waits {
private final SeleniumWebConnector connector;
private static final int LAZY_ELEMENT_RENDER_TIMEOUT = 5;
public Waits(SeleniumWebConnector connector) {
this.connector = connector;
}
public void waitForElements(int timeoutSeconds, Lookup lookup, Until until, String... elementsToCheck) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
wait.ignoring(WebDriverException.class);
Arrays.stream(elementsToCheck)
.forEach(path -> wait.until(resolveUntil(until).apply(byLocator(lookup, path))));
}
public void waitForAtLeastOneElement(int timeoutSeconds, Lookup lookup, String... elementsToCheck) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
ExpectedCondition[] expectedConditions = Arrays.stream(elementsToCheck)
.map(path ->
ExpectedConditions.visibilityOfElementLocated(byLocator(lookup, path)))
.toArray(ExpectedCondition[]::new);
wait.until(ExpectedConditions.or(expectedConditions));
}
WebElement waitForLazyElement(String path, Lookup lookup) {
doWaitForLazyElement(path, lookup);
return connector.getDriver()
.findElement(byLocator(lookup, path));
}
List<WebElement> waitForLazyElements(String path, Lookup lookup) {
doWaitForLazyElement(path, lookup);
return connector.getDriver()
.findElements(byLocator(lookup, path));
}
private void doWaitForLazyElement(String path, Lookup lookup) {
new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(LAZY_ELEMENT_RENDER_TIMEOUT))
.pollingEvery(Duration.ofMillis(500))
.ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class)
.withMessage("Element/s not found within " + LAZY_ELEMENT_RENDER_TIMEOUT
+ " seconds timeout. If this error occurs, you might want to consider using explicit @Wait annotation. " + path)
.until(driver -> !driver.findElements(byLocator(lookup, path)).isEmpty());
}
public void waitForElementAndRefresh(String waitElementPath, String refreshElementPath, Lookup waitElementLookup,
Lookup refreshElementLookup, int timeoutSeconds, int pollingEverySeconds) {
Runnable refreshFunction = () -> connector.clicks().click(() -> refreshElementPath, refreshElementLookup);
waitForElementAndRefresh(waitElementPath, waitElementLookup, refreshFunction, timeoutSeconds,
pollingEverySeconds, true);
// waitForLazyElement(refreshElementPath, refreshElementLookup).click();
}
public void waitForElementAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction,
int timeoutSeconds, int pollingEverySeconds, boolean shouldBeWaitElementVisible) {
new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(timeoutSeconds))
.pollingEvery(Duration.ofSeconds(pollingEverySeconds))
.ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class)
.ignoring(TimeoutException.class)
.withMessage("Element/s not present within " + timeoutSeconds + " seconds timeout. Wait and refreshed " +
"every: " + pollingEverySeconds + " seconds.\n For element: " + waitElementPath)
.until(driver -> {
if (shouldBeWaitElementVisible == true) {
return checkIfElementPresentAndRefresh(waitElementPath, waitElementLookup, refreshFunction, driver);
} else {
return checkIfElementDisappearAndRefresh(waitElementPath, waitElementLookup, refreshFunction, driver);
}
});
}
private WebElement checkIfElementPresentAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction, WebDriver driver) {
Optional.of(driver.findElements(byLocator(waitElementLookup, waitElementPath)))
.filter(Collection::isEmpty)
.ifPresent(col -> refreshFunction.run());
return driver.findElement(byLocator(waitElementLookup, waitElementPath));
}
private boolean checkIfElementDisappearAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction, WebDriver driver) {
Optional.of(driver.findElements(byLocator(waitElementLookup, waitElementPath)))
.ifPresent(col -> refreshFunction.run());
return driver.findElements(byLocator(waitElementLookup, waitElementPath)).size() == 0;
}
public void waitOrFailOnErrorElement(int timeoutSeconds, String pathForWaiting, String pathToFail, Lookup lookup) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
wait.withTimeout(Duration.ofSeconds(timeoutSeconds))
.pollingEvery(Duration.ofSeconds(3))
.withMessage("Element " + pathForWaiting + " or " + pathToFail + " isn't present after " + timeoutSeconds + " seconds")
.until(ExpectedConditions.or(
visibilityOfElementLocated(lookup, pathForWaiting),
visibilityOfElementLocated(lookup, pathToFail)));
if (connector.elementsChecks().isElementPresent(pathToFail, lookup)) {
throw new HarnessException(pathToFail + " is present instead of " + pathForWaiting);
}
}
/**
* Method for wait if all elements passed as parameter in defined state (Visible, Gone, Present in DOM) and there is not
* visible fail element. Fail element is for example error screen.
* <p>
* Wait is implemented as fluent wait with 500ms polling and all parameters are transformed into expected conditions.
* These conditions are used in fluent wait.
* </p>
* <p>
* In case of fail element visible is thrown FailElementDisplayedException.
* </p>
*
* @param timeoutSeconds - wait timeout
* @param lookup - define locator strategy
* @param until - state to check - Visible, Gone, Present in DOM
* @param xpathToFail - xpath for fail element/error screen
* @param elementsToCheck - elements to be wait for
*/
public void waitOrFailOnErrorXpath(int timeoutSeconds, Lookup lookup, Until until, String xpathToFail, String... elementsToCheck) {
ExpectedCondition[] conditionsToCheck = Arrays.stream(elementsToCheck)
.map(elementToCheck -> resolveUntil(until).apply(byLocator(lookup, elementToCheck)))
.toArray(ExpectedCondition[]::new);
ExpectedCondition[] failElementNotDisplayed = new ExpectedCondition[]{BooleanExpectedConditions.failElementNotDisplayed(byLocator(Lookup.XPATH, xpathToFail))};
ExpectedCondition[] conditionsToEvaluate = ArrayUtils.addAll(failElementNotDisplayed, conditionsToCheck);
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
wait.withTimeout(Duration.ofSeconds(timeoutSeconds))
.pollingEvery(Duration.ofMillis(500L))
.withMessage(getErrorElementsMessage(timeoutSeconds, elementsToCheck))
.until(ExpectedConditions.and(conditionsToEvaluate));
}
private String getErrorElementsMessage(int timeoutSeconds, String... elements) {
String elementsInString = String.join(",", elements);
return "Some of listed elements isn't present after " + timeoutSeconds + " seconds. Elements: " + elementsInString;
}
public By byLocator(Lookup lookup, String path) {
return connector.resolveLookup(lookup).apply(path);
}
private ExpectedCondition<WebElement> visibilityOfElementLocated(Lookup lookup, String path) {
return ExpectedConditions.visibilityOfElementLocated(byLocator(lookup, path));
}
private Function<By, ExpectedCondition<?>> resolveUntil(Until until) {
switch (until) {
case VISIBLE:
return ExpectedConditions::visibilityOfElementLocated;
case PRESENT_IN_DOM:
return ExpectedConditions::presenceOfElementLocated;
case GONE:
return ExpectedConditions::invisibilityOfElementLocated;
case CLICKABLE:
return ExpectedConditions::elementToBeClickable;
default:
return ExpectedConditions::presenceOfElementLocated;
}
}
}

View File

@ -1,65 +1,65 @@
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.exception.HarnessException;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.Optional;
public class Windows {
private final SeleniumWebConnector connector;
public Windows(SeleniumWebConnector connector) {
this.connector = connector;
}
public void switchToOtherWindow(int timeout, int expectedNumberOfWindows) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeout));
wait.until(ExpectedConditions.numberOfWindowsToBe(expectedNumberOfWindows));
connector.getDriver()
.getWindowHandles()
.stream()
.filter(w -> getCurrentWindow().map(cw -> !cw.equals(w)).orElse(true))
.findFirst()
.map(otherWindow -> connector.getDriver().switchTo().window(otherWindow))
.orElseThrow(() -> new HarnessException(
"Not possible to switch to other window. Only current window is present."));
}
public void switchToWindowByTitle(String title) {
for (String winHandle : connector.getDriver().getWindowHandles()) {
if (connector.getDriver().switchTo().window(winHandle).getTitle().equals(title)) {
return;
}
}
throw new HarnessException("A browser window named \"" + title + "\" was not found");
}
public void switchWindowResolution(int width, int height) {
WebDriver.Window window = connector.getDriver().manage().window();
Dimension targetSize = new Dimension(width, height);
window.setSize(targetSize);
if (!targetSize.equals(window.getSize())) {
throw new IllegalStateException(String.format("Failed to switch window size to %d %d", width, height));
}
}
private Optional<String> getCurrentWindow() {
try {
return Optional.of(connector
.getDriver()
.getWindowHandle());
} catch (NoSuchWindowException e) {
return Optional.empty();
}
}
public void closeCurrentWindow() {
connector.getDriver().close();
}
}
package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.exception.HarnessException;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.Optional;
public class Windows {
private final SeleniumWebConnector connector;
public Windows(SeleniumWebConnector connector) {
this.connector = connector;
}
public void switchToOtherWindow(int timeout, int expectedNumberOfWindows) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeout));
wait.until(ExpectedConditions.numberOfWindowsToBe(expectedNumberOfWindows));
connector.getDriver()
.getWindowHandles()
.stream()
.filter(w -> getCurrentWindow().map(cw -> !cw.equals(w)).orElse(true))
.findFirst()
.map(otherWindow -> connector.getDriver().switchTo().window(otherWindow))
.orElseThrow(() -> new HarnessException(
"Not possible to switch to other window. Only current window is present."));
}
public void switchToWindowByTitle(String title) {
for (String winHandle : connector.getDriver().getWindowHandles()) {
if (connector.getDriver().switchTo().window(winHandle).getTitle().equals(title)) {
return;
}
}
throw new HarnessException("A browser window named \"" + title + "\" was not found");
}
public void switchWindowResolution(int width, int height) {
WebDriver.Window window = connector.getDriver().manage().window();
Dimension targetSize = new Dimension(width, height);
window.setSize(targetSize);
if (!targetSize.equals(window.getSize())) {
throw new IllegalStateException(String.format("Failed to switch window size to %d %d", width, height));
}
}
private Optional<String> getCurrentWindow() {
try {
return Optional.of(connector
.getDriver()
.getWindowHandle());
} catch (NoSuchWindowException e) {
return Optional.empty();
}
}
public void closeCurrentWindow() {
connector.getDriver().close();
}
}

View File

@ -1,78 +1,78 @@
package cz.moneta.test.harness.connectors.wso2;
import cz.moneta.test.harness.connectors.rest.BaseRestConnector;
import cz.moneta.test.harness.connectors.rest.RemoteRestCallRequest;
import cz.moneta.test.harness.connectors.rest.ResponseHandler;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.context.ConfigAccessor;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
@Deprecated
public class RemoteWso2Connector extends BaseRestConnector implements RestConnector {
private final WebTarget target;
public RemoteWso2Connector(ConfigAccessor store) {
try {
Client client = createHttpClient("RemoteWso2Connector");
this.target = Optional.ofNullable(store.getConfig("selenium.grid.shared.url"))
.map(client::target)
.map(t -> t.path("grid/admin"))
.map(t -> t.path(Wso2ConnectorServlet.class.getSimpleName()))
.orElseThrow(() -> new IllegalStateException("Failed to initialize remote Wso2 connector."));
} catch (Exception e) {
throw new IllegalStateException("Failed to initialize remote Wso2 connector.", e);
}
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType,
Map<String, Object> headers) {
RemoteRestCallRequest get = new RemoteRestCallRequest(path, properties, headers);
return sendAndReceive("call-get", get, responseType);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
RemoteRestCallRequest post = new RemoteRestCallRequest(path, request, headers);
return sendAndReceive("call-post", post, responseType);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType,
Map<String, Object> headers) {
RemoteRestCallRequest delete = new RemoteRestCallRequest(path, properties, headers);
return sendAndReceive("call-delete", delete, responseType);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
RemoteRestCallRequest patch = new RemoteRestCallRequest(path, request, headers);
return sendAndReceive("call-patch", patch, responseType);
}
@Override
public RestConnector registerResponseHandler(
ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> responseHandler) {
throw new UnsupportedOperationException("RemoteWso2Connector#registerResponseHandler not implemented");
}
@SuppressWarnings("unchecked")
private <T> Pair<Integer, T> sendAndReceive(String method, RemoteRestCallRequest request, GenericType<T> responseType) {
Response response = target.path(method)
.request()
.post(Entity.json(request));
return Pair.of(response.getStatus(), response.readEntity(responseType));
}
}
package cz.moneta.test.harness.connectors.wso2;
import cz.moneta.test.harness.connectors.rest.BaseRestConnector;
import cz.moneta.test.harness.connectors.rest.RemoteRestCallRequest;
import cz.moneta.test.harness.connectors.rest.ResponseHandler;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.context.ConfigAccessor;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
@Deprecated
public class RemoteWso2Connector extends BaseRestConnector implements RestConnector {
private final WebTarget target;
public RemoteWso2Connector(ConfigAccessor store) {
try {
Client client = createHttpClient("RemoteWso2Connector");
this.target = Optional.ofNullable(store.getConfig("selenium.grid.shared.url"))
.map(client::target)
.map(t -> t.path("grid/admin"))
.map(t -> t.path(Wso2ConnectorServlet.class.getSimpleName()))
.orElseThrow(() -> new IllegalStateException("Failed to initialize remote Wso2 connector."));
} catch (Exception e) {
throw new IllegalStateException("Failed to initialize remote Wso2 connector.", e);
}
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType,
Map<String, Object> headers) {
RemoteRestCallRequest get = new RemoteRestCallRequest(path, properties, headers);
return sendAndReceive("call-get", get, responseType);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
RemoteRestCallRequest post = new RemoteRestCallRequest(path, request, headers);
return sendAndReceive("call-post", post, responseType);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType,
Map<String, Object> headers) {
RemoteRestCallRequest delete = new RemoteRestCallRequest(path, properties, headers);
return sendAndReceive("call-delete", delete, responseType);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
RemoteRestCallRequest patch = new RemoteRestCallRequest(path, request, headers);
return sendAndReceive("call-patch", patch, responseType);
}
@Override
public RestConnector registerResponseHandler(
ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> responseHandler) {
throw new UnsupportedOperationException("RemoteWso2Connector#registerResponseHandler not implemented");
}
@SuppressWarnings("unchecked")
private <T> Pair<Integer, T> sendAndReceive(String method, RemoteRestCallRequest request, GenericType<T> responseType) {
Response response = target.path(method)
.request()
.post(Entity.json(request));
return Pair.of(response.getStatus(), response.readEntity(responseType));
}
}

View File

@ -1,33 +1,33 @@
package cz.moneta.test.harness.connectors.wso2;
import cz.moneta.test.harness.connectors.rest.ResponseHandler;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.HttpStatus;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.core.Response;
import java.util.function.Function;
import java.util.function.Supplier;
public class TokenRenewalResponseHandler implements ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> {
private final Supplier<String> tokenSupplier;
public TokenRenewalResponseHandler(Supplier<String> tokenSupplier) {
this.tokenSupplier = tokenSupplier;
}
/**
* Checks whether the HTTP response status is 401 UNAUTHORIZED in which case it resends the original request with
* a renewed Authorization Bearer token
*/
@Override
public Response handle(Pair<Invocation.Builder, Function<Invocation.Builder, Response>> invocation, Response response) {
if (response.getStatus() == HttpStatus.SC_UNAUTHORIZED) {
Invocation.Builder builder = invocation.getLeft().header("Authorization", "Bearer " + tokenSupplier.get());
return invocation.getRight().apply(builder);
} else {
return response;
}
}
}
package cz.moneta.test.harness.connectors.wso2;
import cz.moneta.test.harness.connectors.rest.ResponseHandler;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.HttpStatus;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.core.Response;
import java.util.function.Function;
import java.util.function.Supplier;
public class TokenRenewalResponseHandler implements ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> {
private final Supplier<String> tokenSupplier;
public TokenRenewalResponseHandler(Supplier<String> tokenSupplier) {
this.tokenSupplier = tokenSupplier;
}
/**
* Checks whether the HTTP response status is 401 UNAUTHORIZED in which case it resends the original request with
* a renewed Authorization Bearer token
*/
@Override
public Response handle(Pair<Invocation.Builder, Function<Invocation.Builder, Response>> invocation, Response response) {
if (response.getStatus() == HttpStatus.SC_UNAUTHORIZED) {
Invocation.Builder builder = invocation.getLeft().header("Authorization", "Bearer " + tokenSupplier.get());
return invocation.getRight().apply(builder);
} else {
return response;
}
}
}

View File

@ -1,95 +1,95 @@
package cz.moneta.test.harness.connectors.wso2;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import cz.moneta.test.harness.connectors.common.ServletConfigAccessor;
import cz.moneta.test.harness.connectors.rest.RemoteRestCallRequest;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.ConfigAccessor;
import org.apache.commons.lang3.tuple.Pair;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class Wso2ConnectorServlet extends HttpServlet {
private static final ObjectMapper JACKSON = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.registerModules(new ParameterNamesModule(), new Jdk8Module(), new JavaTimeModule());
private final ConfigAccessor store;
private final SimpleRestConnector connector;
public Wso2ConnectorServlet() {
this.store = new ServletConfigAccessor();
this.connector = new SimpleRestConnector(
store.getConfig("endpoints.wso2.url"), "Wso2ConnectorServlet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
Optional.ofNullable(req.getPathInfo())
.flatMap(pi -> Stream.<Supplier<Optional<Supplier<Pair<Integer, String>>>>>of(
() -> Optional.of(pi).filter(i -> i.contains("/call-get")).map(i -> () -> callGet(req)),
() -> Optional.of(pi).filter(i -> i.contains("/call-post")).map(i -> () -> callPost(req)))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst())
.map(Supplier::get)
.ifPresent(r -> mapOntoResponse(r, resp));
}
private void mapOntoResponse(Pair<Integer, String> result, HttpServletResponse resp) {
try {
resp.setContentType("application/json");
resp.setStatus(result.getLeft());
resp.getWriter().write(result.getRight());
} catch (IOException e) {
e.printStackTrace(); //TODO logging
}
}
private Pair<Integer, String> callGet(HttpServletRequest req) {
try {
RemoteRestCallRequest request = JACKSON.readValue(req.getInputStream(), RemoteRestCallRequest.class);
return connector.get(
request.getPath(),
request.getGetProperties(),
new GenericType<>(String.class),
request.getHeaders());
} catch (Exception e) {
e.printStackTrace(); //TODO logging
return null;
}
}
private Pair<Integer, String> callPost(HttpServletRequest req) {
try {
RemoteRestCallRequest request = JACKSON.readValue(req.getInputStream(), RemoteRestCallRequest.class);
return connector.post(
request.getPath(),
Entity.json(request.getRequest()),
new GenericType<>(String.class),
request.getHeaders());
} catch (Exception e) {
e.printStackTrace(); //TODO logging
return null;
}
}
@Override
public void destroy() {
connector.close();
}
}
package cz.moneta.test.harness.connectors.wso2;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import cz.moneta.test.harness.connectors.common.ServletConfigAccessor;
import cz.moneta.test.harness.connectors.rest.RemoteRestCallRequest;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.ConfigAccessor;
import org.apache.commons.lang3.tuple.Pair;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class Wso2ConnectorServlet extends HttpServlet {
private static final ObjectMapper JACKSON = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.registerModules(new ParameterNamesModule(), new Jdk8Module(), new JavaTimeModule());
private final ConfigAccessor store;
private final SimpleRestConnector connector;
public Wso2ConnectorServlet() {
this.store = new ServletConfigAccessor();
this.connector = new SimpleRestConnector(
store.getConfig("endpoints.wso2.url"), "Wso2ConnectorServlet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
Optional.ofNullable(req.getPathInfo())
.flatMap(pi -> Stream.<Supplier<Optional<Supplier<Pair<Integer, String>>>>>of(
() -> Optional.of(pi).filter(i -> i.contains("/call-get")).map(i -> () -> callGet(req)),
() -> Optional.of(pi).filter(i -> i.contains("/call-post")).map(i -> () -> callPost(req)))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst())
.map(Supplier::get)
.ifPresent(r -> mapOntoResponse(r, resp));
}
private void mapOntoResponse(Pair<Integer, String> result, HttpServletResponse resp) {
try {
resp.setContentType("application/json");
resp.setStatus(result.getLeft());
resp.getWriter().write(result.getRight());
} catch (IOException e) {
e.printStackTrace(); //TODO logging
}
}
private Pair<Integer, String> callGet(HttpServletRequest req) {
try {
RemoteRestCallRequest request = JACKSON.readValue(req.getInputStream(), RemoteRestCallRequest.class);
return connector.get(
request.getPath(),
request.getGetProperties(),
new GenericType<>(String.class),
request.getHeaders());
} catch (Exception e) {
e.printStackTrace(); //TODO logging
return null;
}
}
private Pair<Integer, String> callPost(HttpServletRequest req) {
try {
RemoteRestCallRequest request = JACKSON.readValue(req.getInputStream(), RemoteRestCallRequest.class);
return connector.post(
request.getPath(),
Entity.json(request.getRequest()),
new GenericType<>(String.class),
request.getHeaders());
} catch (Exception e) {
e.printStackTrace(); //TODO logging
return null;
}
}
@Override
public void destroy() {
connector.close();
}
}

View File

@ -1,21 +1,22 @@
package cz.moneta.test.harness.constants;
public final class HarnessConfigConstants {
public static final String TEST_UNIQUE_ID = "TEST_UNIQUE_ID";
public static final String TEST_SHORT_ID = "TEST_SHORT_ID";
public static final String VAULT_USERNAME_CONFIG = "vault.username";
public static final String VAULT_PASSWORD_CONFIG = "vault.password";
public static final String VAULT_URL_CONFIG = "vault.url";
public static final String VAULT_WSO2_KEYS_PATH = "vault.client.secrets.path";
public static final String VAULT_CAGW_KEYS_PATH = "vault.cagw.client.secrets.path";
public static final String ENVIRONMENT_TYPE = "environment.type";
public static final String JENKINS_BUILD_URL = "jenkins.build.url";
public static final String SELENIUM_DOWNLOAD_DIRECTORY = "selenium.download.directory";
public static final String SELENIUM_GRID_PLATFORM = "selenium.grid.platform";
public static final long DEFAULT_REST_READ_TIMEOUT = 10L;
private HarnessConfigConstants() {
}
}
package cz.moneta.test.harness.constants;
public final class HarnessConfigConstants {
public static final String TEST_UNIQUE_ID = "TEST_UNIQUE_ID";
public static final String TEST_SHORT_ID = "TEST_SHORT_ID";
public static final String VAULT_USERNAME_CONFIG = "vault.username";
public static final String VAULT_PASSWORD_CONFIG = "vault.password";
public static final String VAULT_URL_CONFIG = "vault.url";
public static final String VAULT_KAFKA_KEYS_CONFIG = "vault.kafka.secrets.path";
public static final String VAULT_WSO2_KEYS_PATH = "vault.client.secrets.path";
public static final String VAULT_CAGW_KEYS_PATH = "vault.cagw.client.secrets.path";
public static final String ENVIRONMENT_TYPE = "environment.type";
public static final String JENKINS_BUILD_URL = "jenkins.build.url";
public static final String SELENIUM_DOWNLOAD_DIRECTORY = "selenium.download.directory";
public static final String SELENIUM_GRID_PLATFORM = "selenium.grid.platform";
public static final long DEFAULT_REST_READ_TIMEOUT = 10L;
private HarnessConfigConstants() {
}
}

View File

@ -1,204 +1,204 @@
package cz.moneta.test.harness.context;
import cz.moneta.test.harness.annotations.TestContext;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.exception.HarnessConfigurationException;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.data.Generator;
import cz.moneta.test.harness.support.data.GeneratorType;
import cz.moneta.test.harness.support.util.Level;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cz.moneta.test.harness.HarnessJunit5Extension.ACTIVE_ENDPOINTS;
@SuppressWarnings("unchecked")
@TestContext
public abstract class BaseStoreAccessor implements StoreAccessor {
private static final Logger LOG = LogManager.getLogger("Harness");
private final Store rootStore;
private final Store globalStore;
private final Store endpointStore;
private final Store configStore;
private final Store generatorsStore;
public BaseStoreAccessor(Store rootStore, Store globalStore, Store endpointStore, Store configStore, Store generatorsStore) {
this.rootStore = rootStore;
this.globalStore = globalStore;
this.endpointStore = endpointStore;
this.configStore = configStore;
this.generatorsStore = generatorsStore;
}
@Override
public <E extends Endpoint> E getEndpoint(Class<E> endpointClass, Object... args) {
Set<Pair<Class<E>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class);
return endpointStore.getOrComputeIfAbsent(
Pair.of(endpointClass, Arrays.asList(args)),
c -> {
try {
Class[] paramTypes = Stream.concat(
Stream.of(StoreAccessor.class),
Arrays.stream(args).map(Object::getClass)).toArray(Class[]::new);
Constructor<E> constructor = endpointClass.getConstructor(paramTypes);
return constructor.newInstance(ArrayUtils.insert(0, args, this));
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw Stream.of(Optional.ofNullable(e.getCause()), Optional.of(e))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.map(t -> new IllegalStateException("Endpoint " + endpointClass.getSimpleName() +
" could not be instantiated: " + t.getMessage(), e)
)
.get();
} finally {
activeEndpoints.add(c);
}
},
endpointClass);
}
@Override
public void closeEndpoint(Endpoint activeEndpoint) {
Set<Pair<Class<Endpoint>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class);
Pair<Class<Endpoint>, List<Object>> pair = activeEndpoints.stream()
.filter(p -> endpointStore.get(p, Endpoint.class) == activeEndpoint)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Cannot find active endpoint to close"));
Endpoint endpoint = endpointStore.get(pair, Endpoint.class);
endpoint.close();
activeEndpoints.remove(pair);
endpointStore.remove(pair, Endpoint.class);
}
@Override
public <E extends Endpoint> Set<E> getActiveEndpoints() {
Set<Pair<Class<E>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class);
return activeEndpoints.stream()
.map(pair -> (E) endpointStore.get(pair, Endpoint.class))
.collect(Collectors.toSet());
}
@Override
public String getConfig(String key) {
return configStore.get(key, String.class);
}
@Override
public String getConfig(String key, String defaultValue) {
return Optional.ofNullable(configStore.get(key, String.class)).orElse(defaultValue);
}
@Override
public void putConfig(String key, String value) {
log(Level.INFO, "Value: " + value + " is stored with key: " + key);
configStore.put(key, value);
}
@Override
public <T> T get(String key) {
return get(key, false);
}
@Override
public <T> T get(String key, boolean nullEnabled) {
if (nullEnabled) {
return (T) globalStore.get(key);
} else {
return (T) Optional.ofNullable(globalStore.get(key))
.orElseThrow(() -> new HarnessException("Error getting value from store, key: " + key + " is null\nCheck if value in this key is correctly set"));
}
}
@Override
public void store(String key, Object value) {
log(Level.INFO, "Value: " + value + " is stored with key: " + key);
globalStore.put(key, value);
}
@Override
public void storeGlobal(String key, Object value) {
log(Level.INFO, "Value: " + value + " is stored with key: " + key);
rootStore.put(key, value);
}
@Override
public void storeGlobal(String key, Supplier orCreate) {
log(Level.INFO, "Value: " + orCreate.get().toString() + " is stored with key: " + key);
rootStore.getOrComputeIfAbsent(key, k -> orCreate.get());
}
@Override
public <T> T getGlobal(String key, Supplier<T> orCreate) {
return (T) rootStore.getOrComputeIfAbsent(key, k -> orCreate.get());
}
@Override
public <T> T getGlobal(String key) {
return (T) rootStore.get(key);
}
@Override
public void log(String template, Object... args) {
LOG.info(template, args);
}
@Override
public void log(Level level, String template, Object... args) {
LOG.log(org.apache.logging.log4j.Level.getLevel(level.name()), template, args);
}
protected void addGenerator(GeneratorType type, Generator<?> generator) {
generatorsStore.put(type, generator);
}
@Override
public <T> T generate(GeneratorType type, Object... params) {
return (T) generatorsStore.get(type, Generator.class).generate(this, params);
}
/**
* Get config value from system property or value from config store.
* <p>
* Value get by priority:
* <ol>
* <li>
* System property (e.g. passed by maven -D)
* </li>
* <li>
* Value from config store
* </li>
* </ol>
* </p>
* <p>
* In case of value not found HarnessConfigurationException is thrown.
* </p>
*
* @param key identifier
* @return String value for defined key
*/
public String getSystemOrConfigValue(String key) {
return Stream.of(Optional.ofNullable(System.getProperty(key)), Optional.ofNullable(getConfig(key)))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElseThrow(() -> new HarnessConfigurationException(("You need to configure " + key + " parameter!")));
}
}
package cz.moneta.test.harness.context;
import cz.moneta.test.harness.annotations.TestContext;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.exception.HarnessConfigurationException;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.data.Generator;
import cz.moneta.test.harness.support.data.GeneratorType;
import cz.moneta.test.harness.support.util.Level;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cz.moneta.test.harness.HarnessJunit5Extension.ACTIVE_ENDPOINTS;
@SuppressWarnings("unchecked")
@TestContext
public abstract class BaseStoreAccessor implements StoreAccessor {
private static final Logger LOG = LogManager.getLogger("Harness");
private final Store rootStore;
private final Store globalStore;
private final Store endpointStore;
private final Store configStore;
private final Store generatorsStore;
public BaseStoreAccessor(Store rootStore, Store globalStore, Store endpointStore, Store configStore, Store generatorsStore) {
this.rootStore = rootStore;
this.globalStore = globalStore;
this.endpointStore = endpointStore;
this.configStore = configStore;
this.generatorsStore = generatorsStore;
}
@Override
public <E extends Endpoint> E getEndpoint(Class<E> endpointClass, Object... args) {
Set<Pair<Class<E>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class);
return endpointStore.getOrComputeIfAbsent(
Pair.of(endpointClass, Arrays.asList(args)),
c -> {
try {
Class[] paramTypes = Stream.concat(
Stream.of(StoreAccessor.class),
Arrays.stream(args).map(Object::getClass)).toArray(Class[]::new);
Constructor<E> constructor = endpointClass.getConstructor(paramTypes);
return constructor.newInstance(ArrayUtils.insert(0, args, this));
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw Stream.of(Optional.ofNullable(e.getCause()), Optional.of(e))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.map(t -> new IllegalStateException("Endpoint " + endpointClass.getSimpleName() +
" could not be instantiated: " + t.getMessage(), e)
)
.get();
} finally {
activeEndpoints.add(c);
}
},
endpointClass);
}
@Override
public void closeEndpoint(Endpoint activeEndpoint) {
Set<Pair<Class<Endpoint>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class);
Pair<Class<Endpoint>, List<Object>> pair = activeEndpoints.stream()
.filter(p -> endpointStore.get(p, Endpoint.class) == activeEndpoint)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Cannot find active endpoint to close"));
Endpoint endpoint = endpointStore.get(pair, Endpoint.class);
endpoint.close();
activeEndpoints.remove(pair);
endpointStore.remove(pair, Endpoint.class);
}
@Override
public <E extends Endpoint> Set<E> getActiveEndpoints() {
Set<Pair<Class<E>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class);
return activeEndpoints.stream()
.map(pair -> (E) endpointStore.get(pair, Endpoint.class))
.collect(Collectors.toSet());
}
@Override
public String getConfig(String key) {
return configStore.get(key, String.class);
}
@Override
public String getConfig(String key, String defaultValue) {
return Optional.ofNullable(configStore.get(key, String.class)).orElse(defaultValue);
}
@Override
public void putConfig(String key, String value) {
log(Level.INFO, "Value: " + value + " is stored with key: " + key);
configStore.put(key, value);
}
@Override
public <T> T get(String key) {
return get(key, false);
}
@Override
public <T> T get(String key, boolean nullEnabled) {
if (nullEnabled) {
return (T) globalStore.get(key);
} else {
return (T) Optional.ofNullable(globalStore.get(key))
.orElseThrow(() -> new HarnessException("Error getting value from store, key: " + key + " is null\nCheck if value in this key is correctly set"));
}
}
@Override
public void store(String key, Object value) {
log(Level.INFO, "Value: " + value + " is stored with key: " + key);
globalStore.put(key, value);
}
@Override
public void storeGlobal(String key, Object value) {
log(Level.INFO, "Value: " + value + " is stored with key: " + key);
rootStore.put(key, value);
}
@Override
public void storeGlobal(String key, Supplier orCreate) {
log(Level.INFO, "Value: " + orCreate.get().toString() + " is stored with key: " + key);
rootStore.getOrComputeIfAbsent(key, k -> orCreate.get());
}
@Override
public <T> T getGlobal(String key, Supplier<T> orCreate) {
return (T) rootStore.getOrComputeIfAbsent(key, k -> orCreate.get());
}
@Override
public <T> T getGlobal(String key) {
return (T) rootStore.get(key);
}
@Override
public void log(String template, Object... args) {
LOG.info(template, args);
}
@Override
public void log(Level level, String template, Object... args) {
LOG.log(org.apache.logging.log4j.Level.getLevel(level.name()), template, args);
}
protected void addGenerator(GeneratorType type, Generator<?> generator) {
generatorsStore.put(type, generator);
}
@Override
public <T> T generate(GeneratorType type, Object... params) {
return (T) generatorsStore.get(type, Generator.class).generate(this, params);
}
/**
* Get config value from system property or value from config store.
* <p>
* Value get by priority:
* <ol>
* <li>
* System property (e.g. passed by maven -D)
* </li>
* <li>
* Value from config store
* </li>
* </ol>
* </p>
* <p>
* In case of value not found HarnessConfigurationException is thrown.
* </p>
*
* @param key identifier
* @return String value for defined key
*/
public String getSystemOrConfigValue(String key) {
return Stream.of(Optional.ofNullable(System.getProperty(key)), Optional.ofNullable(getConfig(key)))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.orElseThrow(() -> new HarnessConfigurationException(("You need to configure " + key + " parameter!")));
}
}

View File

@ -1,10 +1,10 @@
package cz.moneta.test.harness.context;
public interface ConfigAccessor {
String getConfig(String key);
String getConfig(String key, String defaultValue);
void putConfig(String key, String value);
}
package cz.moneta.test.harness.context;
public interface ConfigAccessor {
String getConfig(String key);
String getConfig(String key, String defaultValue);
void putConfig(String key, String value);
}

View File

@ -1,98 +1,98 @@
package cz.moneta.test.harness.context;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.support.data.Generator;
import cz.moneta.test.harness.support.data.GeneratorType;
import cz.moneta.test.harness.support.util.Level;
import java.util.Set;
import java.util.function.Supplier;
public interface StoreAccessor extends ConfigAccessor {
/**
* This method is used to get a direct access to an application or system endpoint. Typical usage is:
* <br/>
* <pre>{@code
* public Login openLoginPage() {
* LoansBranchEndpoint endpoint = harness.getEndpoint(LoansBranchEndpoint.class);
* endpoint.openApplication();
* return Builders.newWebFlowBuilder(Login.class, endpoint, harness);
* }
* }</pre>
*
* Subsequent calls to this method within the same test class return the same instance of the endpoint.
* <br/>
* <i><b>NOTE</b></i> calling this method with different arguments results in multiple instance of the same endpoint
* being instantiated
*
* @param endpointClass desired endpoint class
* @param args arguments to be passed to the endpoint implementation constructor
* @return returns an existing or - in case has not been instantiated - new instance of the endpoint
*/
<E extends Endpoint> E getEndpoint(Class<E> endpointClass, Object... args);
void closeEndpoint(Endpoint endpoint);
<E extends Endpoint> Set<E> getActiveEndpoints();
/**
* Returns a value previously stored via the {@link StoreAccessor#store} method within the same test class.
* <pre>{@code
* harness.withUfoBanka()
* .openLoginPage()
* .then(bankaTasks.login().withCredentialsAndTurnOffSignpad(credentials))
* .fillSearchTerm(harness.get(HKO001_MainClientPage.CLIENT_CIF_STORE_KEY))
* .clickSearchByCif()
* }</pre>
*/
<T> T get(String key);
<T> T get(String key, boolean nullEnabled);
/**
* Stores the given value with the key. The key-value pair is kept for the duration of the test class execution.
*/
void store(String key, Object value);
/**
* Works much like {@link StoreAccessor#get} only it gets values from the persistent global storage that is kept
* for the duration of the execution of all test classes
*
* @param key key
* @param orCreate if the key-value pair is not found, the supplier instantiates and stores the value before
* returning it
*/
<T> T getGlobal(String key, Supplier<T> orCreate);
/**
* Works much like {@link StoreAccessor#get} only it gets values from the persistent global storage that is kept
* for the duration of the execution of all test classes
*/
<T> T getGlobal(String key);
/**
* Works much like {@link StoreAccessor#store} only it stores values in the persistent global storage that is kept
* for the duration of the execution of all test classes
*/
void storeGlobal(String key, Object value);
void storeGlobal(String key, Supplier orCreate);
void log(String template, Object... args);
void log(Level level, String template, Object... args);
/**
* Generates a value.
* <p/>
* Typical usage:
* <pre>{@code
* String ico = harness.generate(GeneratorType.ICO);
* }</pre>
* @param type typ of the generator to be used
* @param params parameters to be passed to the {@link Generator#generate} method
*/
<T> T generate(GeneratorType type, Object... params);
}
package cz.moneta.test.harness.context;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.support.data.Generator;
import cz.moneta.test.harness.support.data.GeneratorType;
import cz.moneta.test.harness.support.util.Level;
import java.util.Set;
import java.util.function.Supplier;
public interface StoreAccessor extends ConfigAccessor {
/**
* This method is used to get a direct access to an application or system endpoint. Typical usage is:
* <br/>
* <pre>{@code
* public Login openLoginPage() {
* LoansBranchEndpoint endpoint = harness.getEndpoint(LoansBranchEndpoint.class);
* endpoint.openApplication();
* return Builders.newWebFlowBuilder(Login.class, endpoint, harness);
* }
* }</pre>
*
* Subsequent calls to this method within the same test class return the same instance of the endpoint.
* <br/>
* <i><b>NOTE</b></i> calling this method with different arguments results in multiple instance of the same endpoint
* being instantiated
*
* @param endpointClass desired endpoint class
* @param args arguments to be passed to the endpoint implementation constructor
* @return returns an existing or - in case has not been instantiated - new instance of the endpoint
*/
<E extends Endpoint> E getEndpoint(Class<E> endpointClass, Object... args);
void closeEndpoint(Endpoint endpoint);
<E extends Endpoint> Set<E> getActiveEndpoints();
/**
* Returns a value previously stored via the {@link StoreAccessor#store} method within the same test class.
* <pre>{@code
* harness.withUfoBanka()
* .openLoginPage()
* .then(bankaTasks.login().withCredentialsAndTurnOffSignpad(credentials))
* .fillSearchTerm(harness.get(HKO001_MainClientPage.CLIENT_CIF_STORE_KEY))
* .clickSearchByCif()
* }</pre>
*/
<T> T get(String key);
<T> T get(String key, boolean nullEnabled);
/**
* Stores the given value with the key. The key-value pair is kept for the duration of the test class execution.
*/
void store(String key, Object value);
/**
* Works much like {@link StoreAccessor#get} only it gets values from the persistent global storage that is kept
* for the duration of the execution of all test classes
*
* @param key key
* @param orCreate if the key-value pair is not found, the supplier instantiates and stores the value before
* returning it
*/
<T> T getGlobal(String key, Supplier<T> orCreate);
/**
* Works much like {@link StoreAccessor#get} only it gets values from the persistent global storage that is kept
* for the duration of the execution of all test classes
*/
<T> T getGlobal(String key);
/**
* Works much like {@link StoreAccessor#store} only it stores values in the persistent global storage that is kept
* for the duration of the execution of all test classes
*/
void storeGlobal(String key, Object value);
void storeGlobal(String key, Supplier orCreate);
void log(String template, Object... args);
void log(Level level, String template, Object... args);
/**
* Generates a value.
* <p/>
* Typical usage:
* <pre>{@code
* String ico = harness.generate(GeneratorType.ICO);
* }</pre>
* @param type typ of the generator to be used
* @param params parameters to be passed to the {@link Generator#generate} method
*/
<T> T generate(GeneratorType type, Object... params);
}

View File

@ -1,27 +1,27 @@
package cz.moneta.test.harness.data;
import java.util.Arrays;
public enum Browser {
GOOGLE_CHROME("chrome"),
MS_EDGE("edge"),
NOT_SUPPORTED("not_supported");
private String configName;
Browser(String configName) {
this.configName = configName;
}
public String getConfigName() {
return configName;
}
public static Browser getBrowserByConfigName(String configName) {
return Arrays.stream(values())
.filter(browser -> browser.getConfigName().equals(configName.trim().toLowerCase()))
.findFirst()
.orElse(NOT_SUPPORTED);
}
}
package cz.moneta.test.harness.data;
import java.util.Arrays;
public enum Browser {
GOOGLE_CHROME("chrome"),
MS_EDGE("edge"),
NOT_SUPPORTED("not_supported");
private String configName;
Browser(String configName) {
this.configName = configName;
}
public String getConfigName() {
return configName;
}
public static Browser getBrowserByConfigName(String configName) {
return Arrays.stream(values())
.filter(browser -> browser.getConfigName().equals(configName.trim().toLowerCase()))
.findFirst()
.orElse(NOT_SUPPORTED);
}
}

View File

@ -1,20 +1,20 @@
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.connectors.WsConnector;
import javax.xml.namespace.QName;
import java.net.URL;
public abstract class BaseWsEndpoint implements Endpoint {
private final WsConnector connector;
public BaseWsEndpoint(String address, URL wsdlUrl, QName serviceName) {
connector = new WsConnector(address, wsdlUrl, serviceName);
}
public <RESP> RESP invoke(Object request, Class<RESP> responseClass) {
return connector.invoke(request, responseClass);
}
}
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.connectors.WsConnector;
import javax.xml.namespace.QName;
import java.net.URL;
public abstract class BaseWsEndpoint implements Endpoint {
private final WsConnector connector;
public BaseWsEndpoint(String address, URL wsdlUrl, QName serviceName) {
connector = new WsConnector(address, wsdlUrl, serviceName);
}
public <RESP> RESP invoke(Object request, Class<RESP> responseClass) {
return connector.invoke(request, responseClass);
}
}

View File

@ -1,28 +1,28 @@
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.annotations.Environment;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
public class CommonEndpoint implements Endpoint {
private Environment environment;
public CommonEndpoint(StoreAccessor storeAccessor) {
this.environment = getEnvironmentByConfigName(storeAccessor);
}
private Environment getEnvironmentByConfigName(StoreAccessor storeAccessor) {
String environmentConfigName = System.getProperty(HarnessConfigConstants.ENVIRONMENT_TYPE);
if (environmentConfigName == null) {
environmentConfigName = storeAccessor.getConfig(HarnessConfigConstants.ENVIRONMENT_TYPE);
}
return Environment.fromString(environmentConfigName);
}
public Environment getEnvironment() {
return environment;
}
}
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.annotations.Environment;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
public class CommonEndpoint implements Endpoint {
private Environment environment;
public CommonEndpoint(StoreAccessor storeAccessor) {
this.environment = getEnvironmentByConfigName(storeAccessor);
}
private Environment getEnvironmentByConfigName(StoreAccessor storeAccessor) {
String environmentConfigName = System.getProperty(HarnessConfigConstants.ENVIRONMENT_TYPE);
if (environmentConfigName == null) {
environmentConfigName = storeAccessor.getConfig(HarnessConfigConstants.ENVIRONMENT_TYPE);
}
return Environment.fromString(environmentConfigName);
}
public Environment getEnvironment() {
return environment;
}
}

View File

@ -1,35 +1,35 @@
package cz.moneta.test.harness.endpoints;
/**
* Endpoints are the the primary interface for interacting with the Harness library.
* <p/>
* They serve as unified wrappers around implementation details (i.e. native libraries), so, that no prior knowledge
* of numerous system client libraries is required.
* <p/>
* Common endpoint would hold an instance of a single {@link cz.moneta.test.harness.connectors.Connector Connector}
* responsible for the actual native client library interaction. Such connectors would include REST, WS, ORACLE or
* Selenium ones.
* <p/>
* While implementing an endpoint for a new system, it is a good idea to find a similar endpoint harnessing the
* same/required connector type. For example when implementing an endpoint for a new REST API, one
* could start with modifying {@link cz.moneta.test.harness.endpoints.autoapi.AutoApiEndpoint AutoApiEndpoint}
* or similar
* <p/>
* Some endpoints may require a new Connector implemented for a specific technology which is not handled by Harness yet
* <p/>
* Endpoints are usually instantiated inside a {@code harness.withSomeSystemName()} method which in turn calls
* {@code harness.getEndpoint(SomeSystemEndpoint.class)}
* <p/>
* All endpoints instantiated via the {@link cz.moneta.test.harness.context.StoreAccessor#getEndpoint(Class, Object...)}
* method are automatically closed (i.e. the resources are released) after each test
*/
public interface Endpoint {
default boolean canAccess() {
return true;
}
default void close() {
}
}
package cz.moneta.test.harness.endpoints;
/**
* Endpoints are the the primary interface for interacting with the Harness library.
* <p/>
* They serve as unified wrappers around implementation details (i.e. native libraries), so, that no prior knowledge
* of numerous system client libraries is required.
* <p/>
* Common endpoint would hold an instance of a single {@link cz.moneta.test.harness.connectors.Connector Connector}
* responsible for the actual native client library interaction. Such connectors would include REST, WS, ORACLE or
* Selenium ones.
* <p/>
* While implementing an endpoint for a new system, it is a good idea to find a similar endpoint harnessing the
* same/required connector type. For example when implementing an endpoint for a new REST API, one
* could start with modifying {@link cz.moneta.test.harness.endpoints.autoapi.AutoApiEndpoint AutoApiEndpoint}
* or similar
* <p/>
* Some endpoints may require a new Connector implemented for a specific technology which is not handled by Harness yet
* <p/>
* Endpoints are usually instantiated inside a {@code harness.withSomeSystemName()} method which in turn calls
* {@code harness.getEndpoint(SomeSystemEndpoint.class)}
* <p/>
* All endpoints instantiated via the {@link cz.moneta.test.harness.context.StoreAccessor#getEndpoint(Class, Object...)}
* method are automatically closed (i.e. the resources are released) after each test
*/
public interface Endpoint {
default boolean canAccess() {
return true;
}
default void close() {
}
}

View File

@ -1,190 +1,190 @@
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.connectors.mobile.UnsupportedPlatformException;
import cz.moneta.test.harness.connectors.mobile.android.AndroidConnector;
import cz.moneta.test.harness.connectors.mobile.ios.IosConnector;
import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup;
import cz.moneta.test.harness.support.mobile.share.Direction;
import cz.moneta.test.harness.support.web.Clickable;
import cz.moneta.test.harness.support.web.TextContainer;
import cz.moneta.test.harness.support.web.Until;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class MobileEndpoint<T extends AppiumMobileConnector> implements Endpoint {
protected final ThreadLocal<T> connector;
public MobileEndpoint(Supplier<T> connectorSupplier) {
this.connector = ThreadLocal.withInitial(connectorSupplier);
}
public AndroidConnector getAndroid() {
try {
return (AndroidConnector) connector.get();
} catch (ClassCastException e) {
throw new UnsupportedPlatformException(MobilePlatform.ANDROID);
}
}
public IosConnector getIos() {
try {
return (IosConnector) connector.get();
} catch (ClassCastException e) {
throw new UnsupportedPlatformException(MobilePlatform.IOS);
}
}
public void click(Clickable clickable, MobileLookup mobileLookup) {
connector.get().click(clickable.getPath(), mobileLookup);
}
public void acceptAlert(int timeout) {
connector.get().acceptAlert(timeout);
}
public void dismissAlert(int timeout) {
connector.get().dismissAlert(timeout);
}
public String getText(String path, MobileLookup mobileLookup) {
return connector.get().getText(path, mobileLookup);
}
public String getText(String path) {
return getText(path, MobileLookup.DEFAULT);
}
public void waitForElementsToLoad(int timeoutSeconds, MobileLookup mobileLookup, Until until, String... elementsToCheck) {
connector.get().waitForElements(timeoutSeconds, mobileLookup, until, elementsToCheck);
}
public void sleepSeconds(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
//
}
}
public void scroll(Direction direction) {
switch (direction) {
case UP:
connector.get().scrollUp();
break;
case DOWN:
connector.get().scrollDown();
break;
case RIGHT:
connector.get().scrollRight();
break;
case LEFT:
connector.get().scrollLeft();
break;
default:
throw new HarnessException("unknown direction");
}
}
public void scrollToElement(Direction direction, MobileLookup mobileLookup, String path) {
switch (direction) {
case UP:
connector.get().scrollUpUntil(path, mobileLookup);
break;
case DOWN:
connector.get().scrollDownUntil(path, mobileLookup);
break;
case LEFT:
connector.get().scrollLeftUntil(path, mobileLookup);
break;
case RIGHT:
connector.get().scrollRightUntil(path, mobileLookup);
break;
default:
throw new HarnessException("unknown direction");
}
}
public void swipeViewLeft(){
connector.get().swipeViewLeft();
}
public void swipeViewRight(){
connector.get().swipeViewRight();
}
public void swipeFromToElement(String source, String target, MobileLookup mobileLookup) {
connector.get().swipeFromToElement(source, target, mobileLookup);
}
public void type(TextContainer input, String text, boolean clear, MobileLookup mobileLookup) {
connector.get().type(input, text, clear, mobileLookup);
}
public void type(TextContainer input, String text, MobileLookup mobileLookup) {
type(input, text, true, mobileLookup);
}
public void type(TextContainer input, String text) {
type(input, text, true, MobileLookup.DEFAULT);
}
public void takeSnapshot(String prefix) {
connector.get().takeSnapshot(getFileName(prefix, ".png"));
}
public void saveSources(String viewName) {
connector.get().saveSource(getFileName(viewName, ".xml"));
}
public void captureVideo(String prefix) {
connector.get().captureVideo(getFileName(prefix, ".mp4"));
}
public void resetApp() {
connector.get().resetApp();
}
public void checkElementContent(String id, String content, MobileLookup mobileLookup) {
connector.get().elementsCheck().checkElementContent(id, content, mobileLookup);
}
public void checkElementContent(String id, String content) {
connector.get().elementsCheck().checkElementContent(id, content, MobileLookup.DEFAULT);
}
public void checkElementPresent(String path, MobileLookup mobileLookup){
connector.get().elementsCheck().checkElementPresent(path, mobileLookup);
}
@Override
public void close() {
connector.get().close();
}
private String getFileName(String prefix, String suffix) {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy/(HH.mm.ss)");
Date date = new Date(System.currentTimeMillis());
StringBuilder sb = new StringBuilder();
if (prefix != null) {
sb.append(prefix);
sb.append("-");
}
sb.append(formatter.format(date));
sb.append("-");
sb.append(MobilePlatform.getMobilePlatformFromConfig(connector.get().getStore()));
sb.append("-");
sb.append(connector.get().getDeviceName());
if (suffix != null) {
sb.append(suffix);
}
return sb.toString();
}
}
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.connectors.mobile.UnsupportedPlatformException;
import cz.moneta.test.harness.connectors.mobile.android.AndroidConnector;
import cz.moneta.test.harness.connectors.mobile.ios.IosConnector;
import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup;
import cz.moneta.test.harness.support.mobile.share.Direction;
import cz.moneta.test.harness.support.web.Clickable;
import cz.moneta.test.harness.support.web.TextContainer;
import cz.moneta.test.harness.support.web.Until;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class MobileEndpoint<T extends AppiumMobileConnector> implements Endpoint {
protected final ThreadLocal<T> connector;
public MobileEndpoint(Supplier<T> connectorSupplier) {
this.connector = ThreadLocal.withInitial(connectorSupplier);
}
public AndroidConnector getAndroid() {
try {
return (AndroidConnector) connector.get();
} catch (ClassCastException e) {
throw new UnsupportedPlatformException(MobilePlatform.ANDROID);
}
}
public IosConnector getIos() {
try {
return (IosConnector) connector.get();
} catch (ClassCastException e) {
throw new UnsupportedPlatformException(MobilePlatform.IOS);
}
}
public void click(Clickable clickable, MobileLookup mobileLookup) {
connector.get().click(clickable.getPath(), mobileLookup);
}
public void acceptAlert(int timeout) {
connector.get().acceptAlert(timeout);
}
public void dismissAlert(int timeout) {
connector.get().dismissAlert(timeout);
}
public String getText(String path, MobileLookup mobileLookup) {
return connector.get().getText(path, mobileLookup);
}
public String getText(String path) {
return getText(path, MobileLookup.DEFAULT);
}
public void waitForElementsToLoad(int timeoutSeconds, MobileLookup mobileLookup, Until until, String... elementsToCheck) {
connector.get().waitForElements(timeoutSeconds, mobileLookup, until, elementsToCheck);
}
public void sleepSeconds(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
//
}
}
public void scroll(Direction direction) {
switch (direction) {
case UP:
connector.get().scrollUp();
break;
case DOWN:
connector.get().scrollDown();
break;
case RIGHT:
connector.get().scrollRight();
break;
case LEFT:
connector.get().scrollLeft();
break;
default:
throw new HarnessException("unknown direction");
}
}
public void scrollToElement(Direction direction, MobileLookup mobileLookup, String path) {
switch (direction) {
case UP:
connector.get().scrollUpUntil(path, mobileLookup);
break;
case DOWN:
connector.get().scrollDownUntil(path, mobileLookup);
break;
case LEFT:
connector.get().scrollLeftUntil(path, mobileLookup);
break;
case RIGHT:
connector.get().scrollRightUntil(path, mobileLookup);
break;
default:
throw new HarnessException("unknown direction");
}
}
public void swipeViewLeft(){
connector.get().swipeViewLeft();
}
public void swipeViewRight(){
connector.get().swipeViewRight();
}
public void swipeFromToElement(String source, String target, MobileLookup mobileLookup) {
connector.get().swipeFromToElement(source, target, mobileLookup);
}
public void type(TextContainer input, String text, boolean clear, MobileLookup mobileLookup) {
connector.get().type(input, text, clear, mobileLookup);
}
public void type(TextContainer input, String text, MobileLookup mobileLookup) {
type(input, text, true, mobileLookup);
}
public void type(TextContainer input, String text) {
type(input, text, true, MobileLookup.DEFAULT);
}
public void takeSnapshot(String prefix) {
connector.get().takeSnapshot(getFileName(prefix, ".png"));
}
public void saveSources(String viewName) {
connector.get().saveSource(getFileName(viewName, ".xml"));
}
public void captureVideo(String prefix) {
connector.get().captureVideo(getFileName(prefix, ".mp4"));
}
public void resetApp() {
connector.get().resetApp();
}
public void checkElementContent(String id, String content, MobileLookup mobileLookup) {
connector.get().elementsCheck().checkElementContent(id, content, mobileLookup);
}
public void checkElementContent(String id, String content) {
connector.get().elementsCheck().checkElementContent(id, content, MobileLookup.DEFAULT);
}
public void checkElementPresent(String path, MobileLookup mobileLookup){
connector.get().elementsCheck().checkElementPresent(path, mobileLookup);
}
@Override
public void close() {
connector.get().close();
}
private String getFileName(String prefix, String suffix) {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy/(HH.mm.ss)");
Date date = new Date(System.currentTimeMillis());
StringBuilder sb = new StringBuilder();
if (prefix != null) {
sb.append(prefix);
sb.append("-");
}
sb.append(formatter.format(date));
sb.append("-");
sb.append(MobilePlatform.getMobilePlatformFromConfig(connector.get().getStore()));
sb.append("-");
sb.append(connector.get().getDeviceName());
if (suffix != null) {
sb.append(suffix);
}
return sb.toString();
}
}

View File

@ -1,13 +1,13 @@
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.context.StoreAccessor;
import org.openqa.selenium.By;
public class MonetaPortalEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.moneta-portal.url";
public MonetaPortalEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.context.StoreAccessor;
import org.openqa.selenium.By;
public class MonetaPortalEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.moneta-portal.url";
public MonetaPortalEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}

View File

@ -1,27 +1,27 @@
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.connectors.rest.ExtendedRestResponse;
import cz.moneta.test.harness.context.StoreAccessor;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import java.util.Map;
public interface RestEndpoint extends Endpoint {
<T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers);
//TODO - make jackson pick up the correct generic type rather than a linked hash map
<T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse);
default <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(String path, Entity<?> request,
Class<T> responseType,
Map<String, Object> headers) {
throw new UnsupportedOperationException(String.format("Endpoint %s does not support extended request yet. Talk to the Harness support team about it", this.getClass().getSimpleName()));
}
<T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse);
StoreAccessor getStore();
}
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.connectors.rest.ExtendedRestResponse;
import cz.moneta.test.harness.context.StoreAccessor;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import java.util.Map;
public interface RestEndpoint extends Endpoint {
<T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers);
//TODO - make jackson pick up the correct generic type rather than a linked hash map
<T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse);
default <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(String path, Entity<?> request,
Class<T> responseType,
Map<String, Object> headers) {
throw new UnsupportedOperationException(String.format("Endpoint %s does not support extended request yet. Talk to the Harness support team about it", this.getClass().getSimpleName()));
}
<T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse);
StoreAccessor getStore();
}

View File

@ -1,13 +1,13 @@
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.context.StoreAccessor;
import org.openqa.selenium.By;
public class SmartAutoEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.smart-auto.url";
public SmartAutoEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}
package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.context.StoreAccessor;
import org.openqa.selenium.By;
public class SmartAutoEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.smart-auto.url";
public SmartAutoEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}

View File

@ -1,51 +1,51 @@
package cz.moneta.test.harness.endpoints.aresapi;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
import java.util.Optional;
public class AresApiEndpoint implements RestEndpoint {
private RestConnector restConnector;
private StoreAccessor store;
public AresApiEndpoint(StoreAccessor store) {
this.store = store;
String endpointName = "endpoints.ares-api.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new SimpleRestConnector(url, "AresApiRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with AresApi"));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
package cz.moneta.test.harness.endpoints.aresapi;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
import java.util.Optional;
public class AresApiEndpoint implements RestEndpoint {
private RestConnector restConnector;
private StoreAccessor store;
public AresApiEndpoint(StoreAccessor store) {
this.store = store;
String endpointName = "endpoints.ares-api.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new SimpleRestConnector(url, "AresApiRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with AresApi"));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}

View File

@ -1,50 +1,50 @@
package cz.moneta.test.harness.endpoints.autoapi;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.util.Map;
import java.util.Optional;
public class AutoApiEndpoint implements RestEndpoint {
private RestConnector restConnector;
private StoreAccessor store;
public AutoApiEndpoint(StoreAccessor store) {
this.store = store;
String endpointName = "endpoints.auto-api.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new SimpleRestConnector(url, "AutoApiRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with AutoApi"));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}
package cz.moneta.test.harness.endpoints.autoapi;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.util.Map;
import java.util.Optional;
public class AutoApiEndpoint implements RestEndpoint {
private RestConnector restConnector;
private StoreAccessor store;
public AutoApiEndpoint(StoreAccessor store) {
this.store = store;
String endpointName = "endpoints.auto-api.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new SimpleRestConnector(url, "AutoApiRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with AutoApi"));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}

View File

@ -1,50 +1,50 @@
package cz.moneta.test.harness.endpoints.autoapi;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
import java.util.Optional;
public class PSmartAutoEndpoint implements RestEndpoint {
private RestConnector restConnector;
private StoreAccessor store;
public PSmartAutoEndpoint(StoreAccessor store) {
this.store = store;
String endpointName = "endpoints.psmartauto.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new SimpleRestConnector(url, "PSmartAutoRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with PSmartAuto"));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}
package cz.moneta.test.harness.endpoints.autoapi;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
import java.util.Optional;
public class PSmartAutoEndpoint implements RestEndpoint {
private RestConnector restConnector;
private StoreAccessor store;
public PSmartAutoEndpoint(StoreAccessor store) {
this.store = store;
String endpointName = "endpoints.psmartauto.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new SimpleRestConnector(url, "PSmartAutoRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with PSmartAuto"));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}

View File

@ -1,47 +1,47 @@
package cz.moneta.test.harness.endpoints.autodb;
import cz.moneta.test.harness.connectors.database.OracleConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.support.auth.AuthSupport;
import cz.moneta.test.harness.support.auth.Credentials;
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.Routine;
import org.jooq.TableRecord;
import java.util.Optional;
import java.util.function.Function;
public class AutoDBEndpoint implements Endpoint {
private final ThreadLocal<OracleConnector> autoDbConnector = ThreadLocal.withInitial(this::initConnector);
private final StoreAccessor store;
public AutoDBEndpoint(StoreAccessor store) {
this.store = store;
}
public <R extends TableRecord<R>> R executeDsl(Function<DSLContext, R> query) {
return autoDbConnector.get().executeDsl(query);
}
public <R extends Routine<?>> R executeProcedure(R procedure) {
return autoDbConnector.get().executeProcedure(procedure);
}
public Result<Record> executeSql(String sql) {
return autoDbConnector.get().executeSql(sql);
}
private OracleConnector initConnector() {
String endpointName = "endpoints.auto-db.url";
Credentials credentials = AuthSupport.getCredentials("auto-db", store);
return Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new OracleConnector(url, credentials.getUsername(), credentials.getPassword()))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with Udebs Database"));
}
}
package cz.moneta.test.harness.endpoints.autodb;
import cz.moneta.test.harness.connectors.database.OracleConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.support.auth.AuthSupport;
import cz.moneta.test.harness.support.auth.Credentials;
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.Routine;
import org.jooq.TableRecord;
import java.util.Optional;
import java.util.function.Function;
public class AutoDBEndpoint implements Endpoint {
private final ThreadLocal<OracleConnector> autoDbConnector = ThreadLocal.withInitial(this::initConnector);
private final StoreAccessor store;
public AutoDBEndpoint(StoreAccessor store) {
this.store = store;
}
public <R extends TableRecord<R>> R executeDsl(Function<DSLContext, R> query) {
return autoDbConnector.get().executeDsl(query);
}
public <R extends Routine<?>> R executeProcedure(R procedure) {
return autoDbConnector.get().executeProcedure(procedure);
}
public Result<Record> executeSql(String sql) {
return autoDbConnector.get().executeSql(sql);
}
private OracleConnector initConnector() {
String endpointName = "endpoints.auto-db.url";
Credentials credentials = AuthSupport.getCredentials("auto-db", store);
return Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new OracleConnector(url, credentials.getUsername(), credentials.getPassword()))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with Udebs Database"));
}
}

View File

@ -0,0 +1,14 @@
package cz.moneta.test.harness.endpoints.bankid;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class BankIdSoniaDemoWebEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.bankid.demoapp.url";
public BankIdSoniaDemoWebEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.broadcom;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class BroadcomEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.broadcom.url";
public BroadcomEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
package cz.moneta.test.harness.endpoints.broadcom;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class BroadcomEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.broadcom.url";
public BroadcomEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}

View File

@ -1,59 +1,59 @@
package cz.moneta.test.harness.endpoints.cagw;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import cz.moneta.test.harness.support.auth.AuthSupport;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.util.Map;
public class CaGwEndpoint implements RestEndpoint {
private RestConnector restConnector;
private StoreAccessor store;
public CaGwEndpoint(StoreAccessor store) {
this.store = store;
String url = store.getConfig("endpoints.cagw.url");
if (url == null) {
throw new IllegalStateException("You need to configure endpoints.cagw.url to work with CBL");
}
this.restConnector = new SimpleRestConnector(url, "CaGwRestLogger");
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
public String getClientSecret(String clientKey) {
return AuthSupport.getClientSecret(clientKey, store, HarnessConfigConstants.VAULT_CAGW_KEYS_PATH);
}
@Override
public StoreAccessor getStore() {
return store;
}
}
package cz.moneta.test.harness.endpoints.cagw;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import cz.moneta.test.harness.support.auth.AuthSupport;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.util.Map;
public class CaGwEndpoint implements RestEndpoint {
private RestConnector restConnector;
private StoreAccessor store;
public CaGwEndpoint(StoreAccessor store) {
this.store = store;
String url = store.getConfig("endpoints.cagw.url");
if (url == null) {
throw new IllegalStateException("You need to configure endpoints.cagw.url to work with CBL");
}
this.restConnector = new SimpleRestConnector(url, "CaGwRestLogger");
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
public String getClientSecret(String clientKey) {
return AuthSupport.getClientSecret(clientKey, store, HarnessConfigConstants.VAULT_CAGW_KEYS_PATH);
}
@Override
public StoreAccessor getStore() {
return store;
}
}

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.cashman;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class CashmanEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.cashman.url";
public CashmanEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}
package cz.moneta.test.harness.endpoints.cashman;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class CashmanEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.cashman.url";
public CashmanEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}

View File

@ -0,0 +1,38 @@
package cz.moneta.test.harness.endpoints.cds;
import cz.moneta.test.harness.connectors.database.OracleConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.support.auth.AuthSupport;
import cz.moneta.test.harness.support.auth.Credentials;
import org.jooq.Record;
import org.jooq.Result;
import java.util.Optional;
public class CdsEndpoint implements Endpoint {
private final StoreAccessor store;
private final ThreadLocal<OracleConnector> connector = ThreadLocal.withInitial(this::initConnector);
public CdsEndpoint(StoreAccessor store) {
this.store = store;
}
public Result<Record> executeSql(String sql) {
return connector.get().executeSql(sql);
}
@Override
public boolean canAccess() {
return AuthSupport.hasCredentials("cds", store);
}
private OracleConnector initConnector() {
String endpointName = "endpoints.cds.url";
Credentials credentials = AuthSupport.getCredentials("cds", store);
return Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new OracleConnector(url, credentials.getUsername(), credentials.getPassword()))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with CDS"));
}
}

View File

@ -1,20 +1,20 @@
package cz.moneta.test.harness.endpoints.cebia;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.BaseWsEndpoint;
import javax.xml.namespace.QName;
import java.net.URL;
@Deprecated(since = "unmaintained since update to Java 17")
public class CebiaWsEndpoint extends BaseWsEndpoint {
private static final String CEBIA_ADDRESS = "https://app.cebia.com/IVATEST/Services/IvaServiceActual.svc";
private static final URL CEBIA_WSDL_URL = CebiaWsEndpoint.class.getClassLoader().getResource("ws/IvaServiceActual.wsdl");
private static final QName CEBIA_SERVICE_NAME = new QName("http://schemas.cebia.com/iva/service/", "IvaService");
public CebiaWsEndpoint(StoreAccessor store) {
super(CEBIA_ADDRESS, CEBIA_WSDL_URL, CEBIA_SERVICE_NAME);
}
}
package cz.moneta.test.harness.endpoints.cebia;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.BaseWsEndpoint;
import javax.xml.namespace.QName;
import java.net.URL;
@Deprecated(since = "unmaintained since update to Java 17")
public class CebiaWsEndpoint extends BaseWsEndpoint {
private static final String CEBIA_ADDRESS = "https://app.cebia.com/IVATEST/Services/IvaServiceActual.svc";
private static final URL CEBIA_WSDL_URL = CebiaWsEndpoint.class.getClassLoader().getResource("ws/IvaServiceActual.wsdl");
private static final QName CEBIA_SERVICE_NAME = new QName("http://schemas.cebia.com/iva/service/", "IvaService");
public CebiaWsEndpoint(StoreAccessor store) {
super(CEBIA_ADDRESS, CEBIA_WSDL_URL, CEBIA_SERVICE_NAME);
}
}

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.endpoints.demo;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class DemoChromeWebEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.demo.url";
private static final Browser DEFAULT_BROWSER = Browser.GOOGLE_CHROME;
public DemoChromeWebEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor, DEFAULT_BROWSER);
}
}
package cz.moneta.test.harness.endpoints.demo;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class DemoChromeWebEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.demo.url";
private static final Browser DEFAULT_BROWSER = Browser.GOOGLE_CHROME;
public DemoChromeWebEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor, DEFAULT_BROWSER);
}
}

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.endpoints.demo;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class DemoEdgeWebEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.demo.url";
private static final Browser DEFAULT_BROWSER = Browser.MS_EDGE;
public DemoEdgeWebEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor, DEFAULT_BROWSER);
}
}
package cz.moneta.test.harness.endpoints.demo;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class DemoEdgeWebEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.demo.url";
private static final Browser DEFAULT_BROWSER = Browser.MS_EDGE;
public DemoEdgeWebEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor, DEFAULT_BROWSER);
}
}

View File

@ -1,28 +1,28 @@
package cz.moneta.test.harness.endpoints.demo;
import cz.moneta.test.harness.connectors.DemoConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
public class DemoEndpoint implements Endpoint {
private DemoConnector connector;
public DemoEndpoint(StoreAccessor store) {
connector = new DemoConnector();
String location = store.getConfig("endpoints.demo.spirits.location");
connector.connectToTempDirectoryService(location);
}
public void summonSpirit(String name) {
connector.createFile(name);
}
public boolean spiritPresent(String name) {
return connector.fileExists(name);
}
public void expelSpirit(String name) {
connector.deleteFile(name);
}
}
package cz.moneta.test.harness.endpoints.demo;
import cz.moneta.test.harness.connectors.DemoConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
public class DemoEndpoint implements Endpoint {
private DemoConnector connector;
public DemoEndpoint(StoreAccessor store) {
connector = new DemoConnector();
String location = store.getConfig("endpoints.demo.spirits.location");
connector.connectToTempDirectoryService(location);
}
public void summonSpirit(String name) {
connector.createFile(name);
}
public boolean spiritPresent(String name) {
return connector.fileExists(name);
}
public void expelSpirit(String name) {
connector.deleteFile(name);
}
}

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.dmbsib;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class DmbsIbEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.dmbsib.web.url";
public DmbsIbEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}
package cz.moneta.test.harness.endpoints.dmbsib;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class DmbsIbEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.dmbsib.web.url";
public DmbsIbEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}

View File

@ -1,50 +1,50 @@
package cz.moneta.test.harness.endpoints.elastic;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.util.Map;
import java.util.Optional;
public class ElasticReadEndpoint implements RestEndpoint {
private final RestConnector restConnector;
private final StoreAccessor store;
public ElasticReadEndpoint(StoreAccessor store) {
this.store = store;
String endpointUrl = "endpoints.elastic.read.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointUrl))
.map(url -> new SimpleRestConnector(url, "ElasticRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointUrl + " to work with Elastic Search."));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}
package cz.moneta.test.harness.endpoints.elastic;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.util.Map;
import java.util.Optional;
public class ElasticReadEndpoint implements RestEndpoint {
private final RestConnector restConnector;
private final StoreAccessor store;
public ElasticReadEndpoint(StoreAccessor store) {
this.store = store;
String endpointUrl = "endpoints.elastic.read.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointUrl))
.map(url -> new SimpleRestConnector(url, "ElasticRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointUrl + " to work with Elastic Search."));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}

View File

@ -1,50 +1,50 @@
package cz.moneta.test.harness.endpoints.elastic;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.util.Map;
import java.util.Optional;
public class ElasticWriteEndpoint implements RestEndpoint {
private final RestConnector restConnector;
private final StoreAccessor store;
public ElasticWriteEndpoint(StoreAccessor store) {
this.store = store;
String endpointUrl = "endpoints.elastic.write.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointUrl))
.map(url -> new SimpleRestConnector(url, "ElasticRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointUrl + " to work with Elastic Search."));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}
package cz.moneta.test.harness.endpoints.elastic;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import java.util.Map;
import java.util.Optional;
public class ElasticWriteEndpoint implements RestEndpoint {
private final RestConnector restConnector;
private final StoreAccessor store;
public ElasticWriteEndpoint(StoreAccessor store) {
this.store = store;
String endpointUrl = "endpoints.elastic.write.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointUrl))
.map(url -> new SimpleRestConnector(url, "ElasticRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointUrl + " to work with Elastic Search."));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}

View File

@ -1,17 +1,17 @@
package cz.moneta.test.harness.endpoints.exevido;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class ExevidoEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.exevido.url";
private static final Browser DEFAULT_BROWSER = Browser.GOOGLE_CHROME;
public ExevidoEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::id, storeAccessor, DEFAULT_BROWSER);
}
}
package cz.moneta.test.harness.endpoints.exevido;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class ExevidoEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.exevido.url";
private static final Browser DEFAULT_BROWSER = Browser.GOOGLE_CHROME;
public ExevidoEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::id, storeAccessor, DEFAULT_BROWSER);
}
}

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.finanso;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class FinansoEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.finanso.url";
public FinansoEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
package cz.moneta.test.harness.endpoints.finanso;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class FinansoEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.finanso.url";
public FinansoEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.forte;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class ForteEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.forte.url";
public ForteEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}
package cz.moneta.test.harness.endpoints.forte;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class ForteEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.forte.url";
public ForteEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}

Some files were not shown because too many files have changed in this diff Show More