diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/assertions/EntitiesAsserter.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/assertions/EntitiesAsserter.java index f9cebf76..c34b507a 100644 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/assertions/EntitiesAsserter.java +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/assertions/EntitiesAsserter.java @@ -16,6 +16,7 @@ import java.lang.reflect.Method; import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -23,10 +24,7 @@ public class EntitiesAsserter { public static Boolean areEqual(TEntity expectedObject, TEntity realObject, DateTimeDeltaType deltaType, int deltaQuantity, String... propertiesNotToCompare) { List failedAssertions = assertAreEqualsInternal(expectedObject, realObject, deltaType, deltaQuantity, propertiesNotToCompare); - if (failedAssertions.size() > 1) { - return false; - } - return true; + return failedAssertions.size() <= 1; } public static Boolean areEqual(TEntity expectedObject, TEntity realObject, String... propertiesNotToCompare) { return areEqual(expectedObject, realObject, DateTimeDeltaType.MILLISECONDS, 300, propertiesNotToCompare); @@ -69,13 +67,20 @@ private static List assertAreEqualsInternal(TEntity expecte try { if (currentRealProperty.getReturnType() == LocalDateTime.class) { + assert currentExpectedProperty != null; LocalDateTimeAssert.areEqual( (LocalDateTime)currentExpectedProperty.invoke(expectedObject), (LocalDateTime)currentRealProperty.invoke(realObject), deltaType, deltaQuantity, exceptionMessage); + } else if (currentRealProperty.getReturnType() == OffsetDateTime.class) { + assert currentExpectedProperty != null; + LocalDateTimeAssert.areEqual( + ((OffsetDateTime)currentExpectedProperty.invoke(expectedObject)).toLocalDateTime(), + ((OffsetDateTime)currentRealProperty.invoke(realObject)).toLocalDateTime(), + deltaType, deltaQuantity, exceptionMessage); } else { Assertions.assertEquals( - currentExpectedProperty.invoke(expectedObject), + currentExpectedProperty != null ? currentExpectedProperty.invoke(expectedObject) : null, currentRealProperty.invoke(realObject), exceptionMessage); } diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/assertions/LocalDateTimeAssert.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/assertions/LocalDateTimeAssert.java index 8bc00f9a..d532a159 100644 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/assertions/LocalDateTimeAssert.java +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/assertions/LocalDateTimeAssert.java @@ -37,7 +37,8 @@ else if (actualDate == null){ } } - public static void areEqual(LocalDateTime expectedDate, LocalDateTime actualDate, Duration expectedDelta, String exceptionMessage) throws Exception { + + public static void areEqual(LocalDateTime expectedDate, LocalDateTime actualDate, Duration expectedDelta, String exceptionMessage) { if (expectedDate == null && actualDate == null){ return; } @@ -54,7 +55,7 @@ else if (actualDate == null){ { var message = exceptionMessage+"\nExpected Date: "+expectedDate+", Actual Date: "+actualDate+ " \nExpected Delta: "+expectedDelta+", Actual Delta: "+actualDelta; - throw new Exception(message); + throw new RuntimeException(message); } } private static Duration getTimeSpanDeltaByType(DateTimeDeltaType type, int count) throws UnsupportedOperationException { diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/WebComponent.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/WebComponent.java index 4b17d3f4..fd57b34c 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/WebComponent.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/WebComponent.java @@ -119,7 +119,7 @@ public WebElement getWrappedElement() { try { wrappedElement.isDisplayed(); // checking if getting property throws exception return wrappedElement; - } catch (StaleElementReferenceException | NoSuchElementException | NullPointerException ex) { + } catch (StaleElementReferenceException | NoSuchElementException | NullPointerException | ScriptTimeoutException ex ) { return findElement(); } } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/BrowserConfiguration.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/BrowserConfiguration.java index fe849269..dd7e35d7 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/BrowserConfiguration.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/BrowserConfiguration.java @@ -18,6 +18,7 @@ import org.openqa.selenium.Platform; import java.util.HashMap; +import java.util.Objects; public class BrowserConfiguration { @Setter @Getter private Browser browser; @@ -72,25 +73,18 @@ public BrowserConfiguration(DeviceName deviceName, Lifecycle browserBehavior, St @Override public boolean equals(Object obj) { - if (!(obj instanceof BrowserConfiguration)) - return false; - BrowserConfiguration that = (BrowserConfiguration)obj; - if (!((this.getBrowser() == null) ? (that.getBrowser() == null) : this.getBrowser().equals(that.getBrowser()))) - return false; - if (this.deviceName != that.deviceName) - return false; - if (!(this.getLifecycle() == null ? that.getLifecycle() == null : this.getLifecycle().equals(that.getLifecycle()))) - return false; - if (this.getHeight() != that.getHeight()) - return false; - if (this.getWidth() != that.getWidth()) - return false; - if (this.getVersion() != that.getVersion()) - return false; - if (!(this.getPlatform() == null ? that.getPlatform() == null : this.getPlatform().equals(that.getPlatform()))) - return false; - if (!(this.getDriverOptions() == null ? that.getDriverOptions() == null : this.getDriverOptions().equals(that.getDriverOptions()))) - return false; - return true; + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + BrowserConfiguration that = (BrowserConfiguration) obj; + + if (!Objects.equals(this.getBrowser(), that.getBrowser())) return false; + if (!Objects.equals(this.deviceName, that.deviceName)) return false; + if (!Objects.equals(this.getLifecycle(), that.getLifecycle())) return false; + if (this.getHeight() != that.getHeight()) return false; + if (this.getWidth() != that.getWidth()) return false; + if (this.getVersion() != that.getVersion()) return false; + if (!Objects.equals(this.getPlatform(), that.getPlatform())) return false; + return Objects.equals(this.getDriverOptions(), that.getDriverOptions()); } } \ No newline at end of file diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/BrowserLifecyclePlugin.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/BrowserLifecyclePlugin.java index f36893e2..59a86f27 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/BrowserLifecyclePlugin.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/BrowserLifecyclePlugin.java @@ -109,10 +109,6 @@ private boolean shouldRestartBrowser() { return true; } else if (!previousConfiguration.equals(currentConfiguration)) { return true; - } else if (currentConfiguration.getLifecycle() == Lifecycle.REUSE_IF_STARTED) { - return false; - } else if (currentConfiguration.getLifecycle() == Lifecycle.RESTART_EVERY_TIME) { - return true; } else { return false; } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/EmptyObjectTypeAdapterFactory.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/EmptyObjectTypeAdapterFactory.java new file mode 100644 index 00000000..1acd84b8 --- /dev/null +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/EmptyObjectTypeAdapterFactory.java @@ -0,0 +1,53 @@ +package solutions.bellatrix.web.infrastructure; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class EmptyObjectTypeAdapterFactory implements TypeAdapterFactory { + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + TypeAdapter delegate = gson.getDelegateAdapter(this, type); + + return new TypeAdapter() { + @Override + public void write(JsonWriter out, T value) throws IOException { + delegate.write(out, value); + } + + @Override + public T read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.BEGIN_ARRAY) { + JsonArray arr = new JsonParser().parse(in).getAsJsonArray(); + if (arr.size() == 0) { + if (isCollectionType(type.getType())) { + return delegate.fromJsonTree(arr); + } else { + return delegate.fromJsonTree(new JsonObject()); + } + } + return delegate.fromJsonTree(arr); + } + return delegate.read(in); + } + }; + } + + private boolean isCollectionType(Type type) { + if (type instanceof Class) { + return Collection.class.isAssignableFrom((Class)type); + } else if (type instanceof ParameterizedType) { + return isCollectionType(((ParameterizedType)type).getRawType()); + } + return false; + } +} \ No newline at end of file diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/LocalDateTimeAdapter.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/LocalDateTimeAdapter.java new file mode 100644 index 00000000..eac4cd3e --- /dev/null +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/LocalDateTimeAdapter.java @@ -0,0 +1,22 @@ +package solutions.bellatrix.web.infrastructure; + +import com.google.gson.*; + +import java.lang.reflect.Type; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class LocalDateTimeAdapter implements JsonSerializer, JsonDeserializer { + private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + + @Override + public JsonElement serialize(LocalDateTime src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(formatter.format(src).replace("+00:00", "")); + } + + @Override + public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return LocalDateTime.parse(json.getAsString().replace("+00:00", ""), formatter); + } +} \ No newline at end of file diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/OffsetDateTimeTypeAdapter.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/OffsetDateTimeTypeAdapter.java new file mode 100644 index 00000000..033d116d --- /dev/null +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/OffsetDateTimeTypeAdapter.java @@ -0,0 +1,33 @@ +package solutions.bellatrix.web.infrastructure; + +import com.google.gson.*; + +import java.lang.reflect.Type; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; + +public class OffsetDateTimeTypeAdapter implements JsonSerializer, JsonDeserializer { + private final DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + private final DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + + @Override + public JsonElement serialize(OffsetDateTime src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(formatter.format(src)); + } + + @Override + public OffsetDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + OffsetDateTime result; + try { + result = OffsetDateTime.parse(json.getAsString(), formatter); + } + catch(Exception ex) { + result = LocalDateTime.parse(json.getAsString(), localDateTimeFormatter).atOffset(ZoneOffset.UTC); + } + + return result; + } +} \ No newline at end of file diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/ProxyServer.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/ProxyServer.java index 65ef8c30..f9461bc6 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/ProxyServer.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/ProxyServer.java @@ -13,10 +13,7 @@ package solutions.bellatrix.web.infrastructure; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import com.google.gson.*; import lombok.SneakyThrows; import net.lightbody.bmp.BrowserMobProxyServer; import net.lightbody.bmp.core.har.HarEntry; @@ -37,6 +34,8 @@ import java.lang.reflect.Type; import java.net.ServerSocket; import java.time.Duration; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -55,6 +54,8 @@ public class ProxyServer { HttpStatus.SC_PARTIAL_CONTENT, HttpStatus.SC_MULTI_STATUS); + private static Gson gson; + @SneakyThrows public static int init() { Log.info("Starting Proxy Service..."); @@ -64,6 +65,11 @@ public static int init() { PROXY_SERVER.get().enableHarCaptureTypes(CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT, CaptureType.REQUEST_HEADERS); PORT.set(port); Log.info("Proxy Service Started at Port %s".formatted(port)); + gson = new GsonBuilder() + .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter()) + .registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeTypeAdapter()) + .registerTypeAdapterFactory(new EmptyObjectTypeAdapterFactory()) + .create(); return port; } @@ -164,7 +170,7 @@ public static void waitForResponse(WebDriver driver, String requestPartialUrl, H catch (TimeoutException exception){ String allUrlsString = getSimilarRequestsString(requestPartialUrl, allHarEntries); - throw new AssertionFailedError(String.format("The expected response with request URL '%s' with method %s is not loaded! \r\nSimilar requests: %s", requestPartialUrl, httpMethod, allUrlsString)); + throw new RuntimeException(String.format("The expected response with request URL '%s' with method %s is not loaded! \r\nSimilar requests: %s", requestPartialUrl, httpMethod, allUrlsString)); } } @@ -220,7 +226,7 @@ public static T getLastRequest(Class requestModelClass) { return getCapturedEntries().stream() .map(HarEntry::getRequest) .filter(request -> request.getPostData() != null) - .map(request -> new Gson().fromJson(request.getPostData().getText(), requestModelClass)) + .map(request -> gson.fromJson(request.getPostData().getText(), requestModelClass)) .reduce((first, second) -> second) .orElse(null); } @@ -229,7 +235,7 @@ public static T getLastResponse(Class responseModelClass) { return getCapturedEntries().stream() .map(HarEntry::getResponse) .filter(response -> response.getContent() != null) - .map(response -> new Gson().fromJson(getDataObject(response.getContent().getText()), responseModelClass)) + .map(response -> gson.fromJson(getDataObject(response.getContent().getText()), responseModelClass)) .reduce((first, second) -> second) .orElse(null); } @@ -238,13 +244,13 @@ public static T getRequestByIndex(int index, Class requestModelClass) { var entries = getCapturedEntries(); var harEntry = entries.get(index); String json = harEntry.getRequest().getPostData().getText(); - return new Gson().fromJson(json, requestModelClass); + return gson.fromJson(json, requestModelClass); } public static T getResponseByIndex(int index, Class responseModelClass) { var harEntry = getCapturedEntries().get(index); String json = harEntry.getResponse().getContent().getText(); - return new Gson().fromJson(getDataObject(json), responseModelClass); + return gson.fromJson(getDataObject(json), responseModelClass); } public static T getRequestByUrl(String url, String httpMethod, Class requestModelClass) { @@ -258,7 +264,7 @@ public static T getRequestByUrl(String url, String httpMethod, Class requ } String json = harEntry.getRequest().getPostData().getText(); try { - return new Gson().fromJson(json, requestModelClass); + return gson.fromJson(json, requestModelClass); } catch (Exception e) { throw new RuntimeException("Error occurred while converting json to model. Json was: %s".formatted(json), e); @@ -276,7 +282,7 @@ public static T getRequestByUrl(String url, String httpMethod, Type modelTyp } String json = harEntry.getRequest().getPostData().getText(); try { - return new Gson().fromJson(json, modelType); + return gson.fromJson(json, modelType); } catch (Exception e) { throw new RuntimeException("Error occurred while converting json to model. Json was: %s".formatted(json), e); @@ -295,7 +301,7 @@ public static T getResponseByUrl(String url, String httpMethod, Class res } String json = harEntry.getResponse().getContent().getText(); try { - return new Gson().fromJson(getDataObject(json), responseModelClass); + return gson.fromJson(getDataObject(json), responseModelClass); } catch (Exception ex){ throw new AssertionFailedError("Cannot get JSON body from the string: " + json + ". Error was: " + ex.getMessage()); @@ -313,7 +319,12 @@ public static T getResponseByUrl(String url, String httpMethod, Type respons return null; } String json = harEntry.getResponse().getContent().getText(); - return new Gson().fromJson(getDataObject(json), responseModelType); + try { + return gson.fromJson(getDataObject(json), responseModelType); + } + catch (Exception ex) { + throw new RuntimeException("Failed to Serialize Json to Object: \n Entity Type: %s\n Json was: %s".formatted(responseModelType.getTypeName(), json)); + } } public static void blockRequestByUrl(String url, HttpMethod httpMethod) { diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/services/App.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/services/App.java index da557850..c9cc0801 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/services/App.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/services/App.java @@ -22,31 +22,31 @@ public class App implements AutoCloseable { private boolean disposed = false; public NavigationService navigate() { - return SingletonFactory.getInstance(NavigationService.class); + return new NavigationService(); } public BrowserService browser() { - return SingletonFactory.getInstance(BrowserService.class); + return new BrowserService(); } public CookiesService cookies() { - return SingletonFactory.getInstance(CookiesService.class); + return new CookiesService(); } public DialogService dialogs() { - return SingletonFactory.getInstance(DialogService.class); + return new DialogService(); } public JavaScriptService script() { - return SingletonFactory.getInstance(JavaScriptService.class); + return new JavaScriptService(); } public ComponentCreateService create() { - return SingletonFactory.getInstance(ComponentCreateService.class); + return new ComponentCreateService(); } public ComponentWaitService waitFor() { - return SingletonFactory.getInstance(ComponentWaitService.class); + return new ComponentWaitService(); } public void addDriverOptions(String key, String value) { diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/services/BrowserService.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/services/BrowserService.java index 416e313b..608eb861 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/services/BrowserService.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/services/BrowserService.java @@ -38,6 +38,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.logging.Level; @@ -212,12 +213,13 @@ public void waitForAjax() { long ajaxTimeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitForAjaxTimeout(); long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval(); var javascriptExecutor = (JavascriptExecutor)getWrappedDriver(); + AtomicInteger ajaxConnections = new AtomicInteger(); try { Wait.retry(() -> { var numberOfAjaxConnections = javascriptExecutor.executeScript("return !isNaN(window.$openHTTPs) ? window.$openHTTPs : null"); if (Objects.nonNull(numberOfAjaxConnections)) { - int ajaxConnections = Integer.parseInt(numberOfAjaxConnections.toString()); - if (ajaxConnections > 0) { + ajaxConnections.set(Integer.parseInt(numberOfAjaxConnections.toString())); + if (ajaxConnections.get() > 0) { String message = "Waiting for %s Ajax Connections...".formatted(ajaxConnections); injectInfoNotificationToast(message); Log.info(message); @@ -233,7 +235,7 @@ public void waitForAjax() { TimeoutException.class); } catch (Exception e) { - var message = "Timed out waiting for Ajax connections."; + var message = "Timed out waiting for %s Ajax connections.".formatted(ajaxConnections.get()); Log.error(message); injectErrorNotificationToast(message); } @@ -428,7 +430,7 @@ public void waitForRequest(String partialUrl) { } } - public void tryWaitForResponse(String partialUrl) { + public void tryWaitForResponse(String partialUrl, int additionalTimeoutInSeconds) { try { if(ProxyServer.get() != null) { ProxyServer.waitForResponse(getWrappedDriver(), partialUrl, HttpMethod.GET, 0); @@ -442,6 +444,10 @@ public void tryWaitForResponse(String partialUrl) { } } + public void tryWaitForResponse(String partialUrl) { + tryWaitForResponse(partialUrl, 0); + } + public void waitForAngular() { long angularTimeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitForAngularTimeout(); long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval();