jms -> mq
This commit is contained in:
parent
70c4540c4a
commit
ab7d898b8e
12
test-harness/.gitignore
vendored
12
test-harness/.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
.idea
|
||||
target
|
||||
*.iml
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
.idea
|
||||
target
|
||||
*.iml
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
# Test Harness
|
||||
|
||||
This repo conatins Test Harness, code name "Hercules".
|
||||
# Test Harness
|
||||
|
||||
This repo conatins Test Harness, code name "Hercules".
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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};
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
}
|
||||
@ -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();
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 "";
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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};
|
||||
}
|
||||
|
||||
@ -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};
|
||||
|
||||
}
|
||||
|
||||
@ -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};
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
@ -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};
|
||||
}
|
||||
@ -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};
|
||||
}
|
||||
@ -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 "";
|
||||
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@ -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!")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user