diff --git a/bellatrix.android/src/main/java/solutions/bellatrix/android/infrastructure/DriverService.java b/bellatrix.android/src/main/java/solutions/bellatrix/android/infrastructure/DriverService.java index 44989fe4..87b173d7 100644 --- a/bellatrix.android/src/main/java/solutions/bellatrix/android/infrastructure/DriverService.java +++ b/bellatrix.android/src/main/java/solutions/bellatrix/android/infrastructure/DriverService.java @@ -19,7 +19,6 @@ import io.appium.java_client.remote.MobileCapabilityType; import lombok.SneakyThrows; import org.openqa.selenium.MutableCapabilities; -import org.openqa.selenium.Platform; import org.openqa.selenium.remote.DesiredCapabilities; import solutions.bellatrix.android.configuration.AndroidSettings; import solutions.bellatrix.android.configuration.GridSettings; @@ -30,11 +29,10 @@ import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; import java.net.URL; +import java.time.Duration; import java.util.HashMap; import java.util.Properties; -import java.util.concurrent.TimeUnit; public class DriverService { private static final ThreadLocal DISPOSED; @@ -84,7 +82,7 @@ public static AndroidDriver start(AppConfiguration configuration) { driver = initializeDriverGridMode(gridSettings.get(), testName); } - driver.manage().timeouts().implicitlyWait(androidSettings.getTimeoutSettings().getImplicitWaitTimeout(), TimeUnit.SECONDS); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(androidSettings.getTimeoutSettings().getImplicitWaitTimeout())); WRAPPED_ANDROID_DRIVER.set(driver); solutions.bellatrix.web.infrastructure.DriverService.setWrappedDriver(driver); return driver; diff --git a/bellatrix.core/pom.xml b/bellatrix.core/pom.xml index 80339850..23e2f014 100644 --- a/bellatrix.core/pom.xml +++ b/bellatrix.core/pom.xml @@ -24,7 +24,7 @@ 1.0 - 4.8.1 + 4.11.0 8.3.0 7.7.0 5.9.2 @@ -62,19 +62,16 @@ software.amazon.awssdk textract - com.opencsv opencsv 5.7.1 - com.mailslurp mailslurp-client-java 15.17.1 - commons-io commons-io @@ -83,7 +80,7 @@ com.google.guava guava - 31.1-jre + 33.0.0-jre com.google.code.gson @@ -156,15 +153,5 @@ ${rest.assured.version} compile - - com.azure - azure-security-keyvault-secrets - 4.2.3 - - - com.azure - azure-identity - 1.2.0 - \ No newline at end of file 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 b5b887ed..f9cebf76 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 @@ -61,7 +61,8 @@ private static List assertAreEqualsInternal(TEntity expecte try { currentExpectedProperty = expectedObject.getClass().getMethod(currentRealProperty.getName()); } catch (NoSuchMethodException e) { - failedAssertions.add(e); + System.out.println(String.format("Property %s not found.", currentRealProperty)); +// failedAssertions.add(e); } var exceptionMessage = "The property " + currentRealProperty.getName() + " of class " + realObject.getClass().getSimpleName() + " was not as expected."; @@ -69,8 +70,8 @@ private static List assertAreEqualsInternal(TEntity expecte try { if (currentRealProperty.getReturnType() == LocalDateTime.class) { LocalDateTimeAssert.areEqual( - (LocalDateTime) currentExpectedProperty.invoke(expectedObject), - (LocalDateTime) currentRealProperty.invoke(realObject), + (LocalDateTime)currentExpectedProperty.invoke(expectedObject), + (LocalDateTime)currentRealProperty.invoke(realObject), deltaType, deltaQuantity, exceptionMessage); } else { Assertions.assertEquals( @@ -78,8 +79,14 @@ private static List assertAreEqualsInternal(TEntity expecte currentRealProperty.invoke(realObject), exceptionMessage); } - - } catch (Exception ex) { + } + catch (NoSuchMethodException nsm){ + //ignore this case + } + catch (NullPointerException nsmex){ + //ignore this case + } + catch (Exception ex) { failedAssertions.add(ex); } } diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/configuration/ConfigurationService.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/configuration/ConfigurationService.java index 95be9b2f..f6626baa 100644 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/configuration/ConfigurationService.java +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/configuration/ConfigurationService.java @@ -30,6 +30,10 @@ public final class ConfigurationService { private static String environment; + public static String getEnvironment() { + return environment; + } + public static T get(Class configSection) { T mappedObject = (T)new Object(); if (environment == null) { diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/integrations/azure/KeyVault.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/integrations/azure/KeyVault.java deleted file mode 100644 index dbf5a46f..00000000 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/integrations/azure/KeyVault.java +++ /dev/null @@ -1,43 +0,0 @@ -package solutions.bellatrix.core.integrations.azure; - -import com.azure.identity.DefaultAzureCredentialBuilder; -import com.azure.security.keyvault.secrets.SecretClient; -import com.azure.security.keyvault.secrets.SecretClientBuilder; -import com.azure.security.keyvault.secrets.models.KeyVaultSecret; -import solutions.bellatrix.core.configuration.ConfigurationService; - -public class KeyVault { - private static SecretClient secretClient; - - static { - initializeClient(); - } - - public static boolean isAvailable = secretClient != null; - - public static String getSecret(String name) { - if (secretClient == null) { - return null; - } - - KeyVaultSecret secret = secretClient.getSecret(name); - return secret.getValue(); - } - - private static void initializeClient() { - if (secretClient != null) { - return; - } - - KeyVaultSettings settings = ConfigurationService.get(KeyVaultSettings.class); - - if (!settings.isEnabled() || settings.getKeyVaultEndpoint().isEmpty()) { - return; - } - - secretClient = new SecretClientBuilder() - .vaultUrl(settings.getKeyVaultEndpoint()) - .credential(new DefaultAzureCredentialBuilder().build()) - .buildClient(); - } -} \ No newline at end of file diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/integrations/azure/SecretsResolver.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/integrations/azure/SecretsResolver.java index f7ed3893..b5a0077c 100644 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/integrations/azure/SecretsResolver.java +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/integrations/azure/SecretsResolver.java @@ -10,17 +10,10 @@ public static String getSecret(Supplier getConfigValue) { return System.getenv(configValue.replace("env_", "")); } - if (configValue.startsWith("vault_")) { - return KeyVault.getSecret(configValue.replace("vault_", "")); - } - return configValue; } public static String getSecret(String name) { - if (KeyVault.isAvailable) { - return KeyVault.getSecret(name); - } String environmentalVariable = System.getenv(name); diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/plugins/PluginExecutionEngine.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/plugins/PluginExecutionEngine.java index 448e18e2..3c294646 100644 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/plugins/PluginExecutionEngine.java +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/plugins/PluginExecutionEngine.java @@ -14,12 +14,10 @@ package solutions.bellatrix.core.plugins; import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.*; public final class PluginExecutionEngine { - private final static Set PLUGINS; + private final static LinkedHashSet PLUGINS; static { PLUGINS = new LinkedHashSet<>(); diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/plugins/junit/BaseTest.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/plugins/junit/BaseTest.java index fdf6339a..25569f95 100644 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/plugins/junit/BaseTest.java +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/plugins/junit/BaseTest.java @@ -13,10 +13,7 @@ package solutions.bellatrix.core.plugins.junit; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import solutions.bellatrix.core.plugins.PluginExecutionEngine; import solutions.bellatrix.core.plugins.TestResult; @@ -28,6 +25,7 @@ import java.util.List; @ExtendWith(TestResultWatcher.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class BaseTest extends UsesPlugins { static final ThreadLocal CURRENT_TEST_RESULT = new ThreadLocal<>(); private static final ThreadLocal CONFIGURATION_EXECUTED = new ThreadLocal<>(); @@ -92,7 +90,7 @@ public void afterMethodCore(TestInfo testInfo) { try { var testClass = this.getClass(); assert testInfo.getTestMethod().isPresent(); - var methodInfo = testClass.getMethod(testInfo.getTestMethod().get().getName()); + var methodInfo = testClass.getMethod(testInfo.getTestMethod().get().getName(), testInfo.getTestMethod().get().getParameterTypes()); PluginExecutionEngine.preAfterTest(CURRENT_TEST_RESULT.get(), methodInfo); afterEach(); // PluginExecutionEngine.postAfterTest(CURRENT_TEST_RESULT.get(), methodInfo); @@ -103,11 +101,12 @@ public void afterMethodCore(TestInfo testInfo) { } @AfterAll - public static void afterClassCore(TestInfo testInfo) { + public void afterClassCore(TestInfo testInfo) { try { var testClass = testInfo.getTestClass(); if (testClass.isPresent()) { PluginExecutionEngine.preAfterClass(testClass.get()); + afterClass(); PluginExecutionEngine.postAfterClass(testClass.get()); } } catch (Exception e) { @@ -134,4 +133,7 @@ protected void onBeforeEachFailure() { protected void afterEach() { } + + protected void afterClass() { + } } diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/ClipboardManager.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/ClipboardManager.java new file mode 100644 index 00000000..fe5c2be8 --- /dev/null +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/ClipboardManager.java @@ -0,0 +1,26 @@ +package solutions.bellatrix.core.utilities; + +import java.awt.*; +import java.awt.datatransfer.*; +import java.io.IOException; + +public class ClipboardManager { + private static final Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + + public static String getLastEntity() { + var lastCopiedEntity = ""; + + try { + lastCopiedEntity = (String)systemClipboard.getData(DataFlavor.stringFlavor); + } catch (IOException | UnsupportedFlavorException e) { + throw new RuntimeException(e); + } + + return lastCopiedEntity; + } + + public static void copyTextToClipboard(String text) { + Transferable transferable = new StringSelection(text); + systemClipboard.setContents(transferable, null); + } +} diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/SingletonFactory.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/SingletonFactory.java index 396fdc9c..1988bff3 100644 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/SingletonFactory.java +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/SingletonFactory.java @@ -36,7 +36,7 @@ public static T getInstance(Class classOf, Object... initargs) { return (T)SINGLETON_FACTORY.mapHolder.get(classOf.getName()); } catch (Exception e) { - // not the best practice to return null. But probably we will never end here so it is OK. + Log.error("Failed to create instance of the object. Exception was: " + e); return null; } } diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Wait.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Wait.java index 756f45c0..136a1d80 100644 --- a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Wait.java +++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Wait.java @@ -3,9 +3,12 @@ import java.time.Duration; public class Wait { - public static void retry(Runnable action, int timesToRetry, long sleepInterval, Class ... exceptionsToIgnore) throws InterruptedException { + public static void retry(Runnable action, int timesToRetry, long sleepInterval, Class ... exceptionsToIgnore) { + Wait.retry(action, timesToRetry, sleepInterval, true, exceptionsToIgnore); + } + + public static void retry(Runnable action, int timesToRetry, long sleepInterval,boolean shouldThrowException, Class ... exceptionsToIgnore) { int repeat = 0; - boolean shouldThrowException = true; while(repeat <= timesToRetry) { try { shouldThrowException = true; @@ -17,7 +20,11 @@ public static void retry(Runnable action, int timesToRetry, long sleepInterval, //exc.printStackTrace(); repeat++; shouldThrowException = false; - Thread.sleep(Duration.ofSeconds(sleepInterval).toMillis()); + try { + Thread.sleep(Duration.ofSeconds(sleepInterval).toMillis()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } break; } } @@ -29,12 +36,11 @@ public static void retry(Runnable action, int timesToRetry, long sleepInterval, } } - public static void retry(Runnable action, Class ... exceptionsToIgnore) throws InterruptedException { + public static void retry(Runnable action, Class ... exceptionsToIgnore) { retry(action, Duration.ofSeconds(30), Duration.ofSeconds(1), exceptionsToIgnore); } - public static void retry(Runnable action, Duration timeout, Duration sleepInterval, Class ... exceptionsToIgnore) throws InterruptedException { - boolean shouldThrowException = true; + public static boolean retry(Runnable action, Duration timeout, Duration sleepInterval, Boolean shouldThrowException, Class ... exceptionsToIgnore) { long start = System.currentTimeMillis(); long end = start + timeout.toMillis(); while(System.currentTimeMillis() < end) { @@ -47,7 +53,11 @@ public static void retry(Runnable action, Duration timeout, Duration sleepInterv if (currentException.isInstance(exc)) { //exc.printStackTrace(); shouldThrowException = false; - Thread.sleep(sleepInterval.toMillis()); + try { + Thread.sleep(sleepInterval.toMillis()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } break; } } @@ -55,7 +65,16 @@ public static void retry(Runnable action, Duration timeout, Duration sleepInterv if (shouldThrowException) { throw exc; } + else { + return false; + } } } + + return true; + } + + public static void retry(Runnable action, Duration timeout, Duration sleepInterval, Class ... exceptionsToIgnore) { + Wait.retry(action, timeout, sleepInterval, true, exceptionsToIgnore); } } diff --git a/bellatrix.desktop/src/main/java/solutions/bellatrix/desktop/infrastructure/DriverService.java b/bellatrix.desktop/src/main/java/solutions/bellatrix/desktop/infrastructure/DriverService.java index 83501321..bdcc3dd7 100644 --- a/bellatrix.desktop/src/main/java/solutions/bellatrix/desktop/infrastructure/DriverService.java +++ b/bellatrix.desktop/src/main/java/solutions/bellatrix/desktop/infrastructure/DriverService.java @@ -15,7 +15,10 @@ import io.appium.java_client.windows.WindowsDriver; import lombok.SneakyThrows; -import org.openqa.selenium.*; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.MutableCapabilities; +import org.openqa.selenium.Platform; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.remote.DesiredCapabilities; import solutions.bellatrix.core.configuration.ConfigurationService; import solutions.bellatrix.core.utilities.DebugInformation; @@ -25,8 +28,8 @@ import java.io.File; import java.net.MalformedURLException; import java.net.URL; +import java.time.Duration; import java.util.HashMap; -import java.util.concurrent.TimeUnit; public class DriverService { private static final ThreadLocal DISPOSED; @@ -73,7 +76,7 @@ public static WindowsDriver start(AppConfiguration configuration) { driver = initializeDriverGridMode(gridSettings.get()); } - driver.manage().timeouts().implicitlyWait(desktopSettings.getTimeoutSettings().getImplicitWaitTimeout(), TimeUnit.SECONDS); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(desktopSettings.getTimeoutSettings().getImplicitWaitTimeout())); driver.manage().window().maximize(); changeWindowSize(driver); WRAPPED_DRIVER.set(driver); diff --git a/bellatrix.playwright.getting.started/pom.xml b/bellatrix.playwright.getting.started/pom.xml index 3bac9e40..db4f3b0e 100644 --- a/bellatrix.playwright.getting.started/pom.xml +++ b/bellatrix.playwright.getting.started/pom.xml @@ -9,7 +9,6 @@ 1.0-SNAPSHOT - bellatrix bellatrix.playwright.getting.started diff --git a/bellatrix.plugins.screenshots/src/main/java/plugins/screenshots/ScreenshotPlugin.java b/bellatrix.plugins.screenshots/src/main/java/plugins/screenshots/ScreenshotPlugin.java index c5b64dfa..b31e3805 100644 --- a/bellatrix.plugins.screenshots/src/main/java/plugins/screenshots/ScreenshotPlugin.java +++ b/bellatrix.plugins.screenshots/src/main/java/plugins/screenshots/ScreenshotPlugin.java @@ -35,13 +35,13 @@ public ScreenshotPlugin(boolean isEnabled) { @Override @SneakyThrows - public void preAfterTest(TestResult testResult, Method memberInfo) { + public void postAfterTest(TestResult testResult, Method memberInfo, Throwable failedTestException) { if (!isEnabled || testResult == TestResult.SUCCESS) return; var screenshotSaveDir = getOutputFolder(); var screenshotFileName = getUniqueFileName(memberInfo.getName()); takeScreenshot(screenshotSaveDir, screenshotFileName); - SCREENSHOT_GENERATED.broadcast(new ScreenshotPluginEventArgs(Paths.get(screenshotSaveDir, screenshotFileName).toString())); + SCREENSHOT_GENERATED.broadcast(new ScreenshotPluginEventArgs(Paths.get(screenshotSaveDir, screenshotFileName).toString(), screenshotFileName)); } } diff --git a/bellatrix.plugins.screenshots/src/main/java/plugins/screenshots/ScreenshotPluginEventArgs.java b/bellatrix.plugins.screenshots/src/main/java/plugins/screenshots/ScreenshotPluginEventArgs.java index 3a3a87e7..b0026cc5 100644 --- a/bellatrix.plugins.screenshots/src/main/java/plugins/screenshots/ScreenshotPluginEventArgs.java +++ b/bellatrix.plugins.screenshots/src/main/java/plugins/screenshots/ScreenshotPluginEventArgs.java @@ -15,10 +15,13 @@ import lombok.Getter; +@Getter public class ScreenshotPluginEventArgs { - @Getter private final String screenshotPath; + private final String screenshotPath; + private final String fileName; - public ScreenshotPluginEventArgs(String screenshotPath) { + public ScreenshotPluginEventArgs(String screenshotPath, String fileName) { this.screenshotPath = screenshotPath; + this.fileName = fileName; } } diff --git a/bellatrix.plugins.video/src/main/java/plugins/video/VideoPlugin.java b/bellatrix.plugins.video/src/main/java/plugins/video/VideoPlugin.java index 5b108219..49174e68 100644 --- a/bellatrix.plugins.video/src/main/java/plugins/video/VideoPlugin.java +++ b/bellatrix.plugins.video/src/main/java/plugins/video/VideoPlugin.java @@ -20,9 +20,6 @@ import java.io.File; import java.lang.reflect.Method; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; public abstract class VideoPlugin extends Plugin { public static final EventListener VIDEO_GENERATED = new EventListener<>(); @@ -58,7 +55,6 @@ public void postAfterTest(TestResult testResult, Method memberInfo, Throwable fa } else { try { FileUtils.forceDeleteOnExit(new File(VIDEO_FULL_PATH.get())); - Files.delete(Path.of(VIDEO_FULL_PATH.get())); } catch (Exception ex) { ex.printStackTrace(); } diff --git a/bellatrix.web.getting.started/src/test/java/O12_page_objects/cart_page/Map.java b/bellatrix.web.getting.started/src/test/java/O12_page_objects/cart_page/Map.java index 62186e7e..b558d994 100644 --- a/bellatrix.web.getting.started/src/test/java/O12_page_objects/cart_page/Map.java +++ b/bellatrix.web.getting.started/src/test/java/O12_page_objects/cart_page/Map.java @@ -6,8 +6,8 @@ import java.util.List; public class Map extends PageMap { - public TextField couponCodeTextField() { - return create().byId(TextField.class, "coupon_code"); + public TextInput couponCodeTextField() { + return create().byId(TextInput.class, "coupon_code"); } public Button applyCouponButton() { diff --git a/bellatrix.web.getting.started/src/test/java/O12_page_objects/checkoutpage/Map.java b/bellatrix.web.getting.started/src/test/java/O12_page_objects/checkoutpage/Map.java index 8229aa0f..3ae943a3 100644 --- a/bellatrix.web.getting.started/src/test/java/O12_page_objects/checkoutpage/Map.java +++ b/bellatrix.web.getting.started/src/test/java/O12_page_objects/checkoutpage/Map.java @@ -17,48 +17,48 @@ import solutions.bellatrix.web.pages.PageMap; public class Map extends PageMap { - public TextField billingFirstName() { - return create().byId(TextField.class, "billing_first_name"); + public TextInput billingFirstName() { + return create().byId(TextInput.class, "billing_first_name"); } - public TextField billingLastName() { - return create().byId(TextField.class, "billing_last_name"); + public TextInput billingLastName() { + return create().byId(TextInput.class, "billing_last_name"); } - public TextField billingCompany() { - return create().byId(TextField.class, "billing_company"); + public TextInput billingCompany() { + return create().byId(TextInput.class, "billing_company"); } public Button billingCountryWrapper() { return create().byId(Button.class, "select2-billing_country-container"); } - public TextField billingCountryFilter() { - return create().byClass(TextField.class, "select2-search__field"); + public TextInput billingCountryFilter() { + return create().byClass(TextInput.class, "select2-search__field"); } - public TextField billingAddress1() { - return create().byId(TextField.class, "billing_address_1"); + public TextInput billingAddress1() { + return create().byId(TextInput.class, "billing_address_1"); } - public TextField billingAddress2() { - return create().byId(TextField.class, "billing_address_2"); + public TextInput billingAddress2() { + return create().byId(TextInput.class, "billing_address_2"); } - public TextField billingCity() { - return create().byId(TextField.class, "billing_city"); + public TextInput billingCity() { + return create().byId(TextInput.class, "billing_city"); } - public TextField billingZip() { - return create().byId(TextField.class, "billing_postcode"); + public TextInput billingZip() { + return create().byId(TextInput.class, "billing_postcode"); } - public TextField billingPhone() { - return create().byId(TextField.class, "billing_phone"); + public TextInput billingPhone() { + return create().byId(TextInput.class, "billing_phone"); } - public TextField billingEmail() { - return create().byId(TextField.class, "billing_email"); + public TextInput billingEmail() { + return create().byId(TextInput.class, "billing_email"); } public CheckBox createAccountCheckBox() { diff --git a/bellatrix.web.tests/src/test/java/junit/ProductPurchaseTests.java b/bellatrix.web.tests/src/test/java/junit/ProductPurchaseTests.java index 0e6e40f5..57a2f152 100644 --- a/bellatrix.web.tests/src/test/java/junit/ProductPurchaseTests.java +++ b/bellatrix.web.tests/src/test/java/junit/ProductPurchaseTests.java @@ -37,12 +37,12 @@ public void completePurchaseSuccessfully_first() { var blogLink = app().create().byInnerTextContaining(Anchor.class, "Blog"); addToCartFalcon9.click(); blogLink.above(addToCartFalcon9).validate(); - new MainPage().asserts().productBoxLink("Falcon 9", "http://demos.bellatrix.solutions/product/falcon-9/"); + new MainPage().asserts().productBoxLink("Falcon 9", "https://demos.bellatrix.solutions/product/falcon-9/"); } @Test public void completePurchaseSuccessfully_second() { - app().navigate().to("http://demos.bellatrix.solutions/"); + app().navigate().to("https://demos.bellatrix.solutions/"); var addToCartFalcon9 = app().create().byCss(Anchor.class, "[data-product_id*='28']"); addToCartFalcon9.click(); } @@ -51,14 +51,14 @@ public void completePurchaseSuccessfully_second() { public void falcon9LinkAddsCorrectProduct() { var mainPage = app().goTo(MainPage.class); - mainPage.asserts().productBoxLink("Falcon 9", "http://demos.bellatrix.solutions/product/falcon-9/"); + mainPage.asserts().productBoxLink("Falcon 9", "https://demos.bellatrix.solutions/product/falcon-9/"); } @Test public void saturnVLinkAddsCorrectProduct() { var mainPage = app().goTo(MainPage.class); - mainPage.asserts().productBoxLink("Saturn V", "http://demos.bellatrix.solutions/product/saturn-v/"); + mainPage.asserts().productBoxLink("Saturn V", "https://demos.bellatrix.solutions/product/saturn-v/"); } @Test @@ -66,7 +66,7 @@ public void purchaseFalcon9WithoutFacade() { var mainPage = app().goTo(MainPage.class); mainPage.addRocketToShoppingCart("Falcon 9"); - var cartPage = app().create(CartPage.class); + var cartPage = app().createPage(CartPage.class); cartPage.applyCoupon("happybirthday"); cartPage.asserts().couponAppliedSuccessfully(); cartPage.increaseProductQuantity(2); @@ -85,7 +85,7 @@ public void purchaseFalcon9WithoutFacade() { // purchaseInfo.setZip("10115"); // purchaseInfo.setPhone("+498888999281"); - var checkoutPage = app().create(CheckoutPage.class); + var checkoutPage = app().createPage(CheckoutPage.class); checkoutPage.fillBillingInfo(purchaseInfo); checkoutPage.asserts().orderReceived(); } @@ -95,7 +95,7 @@ public void purchaseSaturnVWithoutFacade() { var mainPage = app().goTo(MainPage.class); mainPage.addRocketToShoppingCart("Saturn V"); - var cartPage = app().create(CartPage.class); + var cartPage = app().createPage(CartPage.class); cartPage.applyCoupon("happybirthday"); cartPage.asserts().couponAppliedSuccessfully(); cartPage.increaseProductQuantity(3); @@ -114,7 +114,7 @@ public void purchaseSaturnVWithoutFacade() { // purchaseInfo.setZip("10115"); // purchaseInfo.setPhone("+498888999281"); - var checkoutPage = app().create(CheckoutPage.class); + var checkoutPage = app().createPage(CheckoutPage.class); checkoutPage.fillBillingInfo(purchaseInfo); checkoutPage.asserts().orderReceived(); } diff --git a/bellatrix.web.tests/src/test/java/testng/ProductPurchaseTests.java b/bellatrix.web.tests/src/test/java/testng/ProductPurchaseTests.java index 948f3ec6..011d6cf9 100644 --- a/bellatrix.web.tests/src/test/java/testng/ProductPurchaseTests.java +++ b/bellatrix.web.tests/src/test/java/testng/ProductPurchaseTests.java @@ -3,7 +3,7 @@ * Author: Anton Angelov * Licensed under the Apache License, Version 2.0 (the "License"); * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -33,17 +33,17 @@ protected void afterEach() { @Test public void completePurchaseSuccessfully_first() { - app().navigate().to("http://demos.bellatrix.solutions/"); + app().navigate().to("https://demos.bellatrix.solutions/"); var addToCartFalcon9 = app().create().byCss(Anchor.class, "[data-product_id*='28']"); var blogLink = app().create().byInnerTextContaining(Anchor.class, "Blog"); addToCartFalcon9.click(); blogLink.above(addToCartFalcon9); - new MainPage().asserts().productBoxLink("Falcon 9", "http://demos.bellatrix.solutions/product/falcon-9/"); + new MainPage().asserts().productBoxLink("Falcon 9", "https://demos.bellatrix.solutions/product/falcon-9/"); } @Test public void completePurchaseSuccessfully_second() { - app().navigate().to("http://demos.bellatrix.solutions/"); + app().navigate().to("https://demos.bellatrix.solutions/"); var addToCartFalcon9 = app().create().byCss(Anchor.class, "[data-product_id*='28']"); addToCartFalcon9.click(); } @@ -52,14 +52,14 @@ public void completePurchaseSuccessfully_second() { public void falcon9LinkAddsCorrectProduct() { var mainPage = app().goTo(MainPage.class); - mainPage.asserts().productBoxLink("Falcon 9", "http://demos.bellatrix.solutions/product/falcon-9/"); + mainPage.asserts().productBoxLink("Falcon 9", "https://demos.bellatrix.solutions/product/falcon-9/"); } @Test public void saturnVLinkAddsCorrectProduct() { var mainPage = app().goTo(MainPage.class); - mainPage.asserts().productBoxLink("Saturn V", "http://demos.bellatrix.solutions/product/saturn-v/"); + mainPage.asserts().productBoxLink("Saturn V", "https://demos.bellatrix.solutions/product/saturn-v/"); } @Test @@ -67,7 +67,7 @@ public void purchaseFalcon9WithoutFacade() { var mainPage = app().goTo(MainPage.class); mainPage.addRocketToShoppingCart("Falcon 9"); - var cartPage = app().create(CartPage.class); + var cartPage = app().createPage(CartPage.class); cartPage.applyCoupon("happybirthday"); cartPage.asserts().couponAppliedSuccessfully(); cartPage.increaseProductQuantity(2); @@ -86,7 +86,7 @@ public void purchaseFalcon9WithoutFacade() { purchaseInfo.setZip("10115"); purchaseInfo.setPhone("+498888999281"); - var checkoutPage = app().create(CheckoutPage.class); + var checkoutPage = app().createPage(CheckoutPage.class); checkoutPage.fillBillingInfo(purchaseInfo); checkoutPage.asserts().orderReceived(); } @@ -96,7 +96,7 @@ public void purchaseSaturnVWithoutFacade() { var mainPage = app().goTo(MainPage.class); mainPage.addRocketToShoppingCart("Saturn V"); - var cartPage = app().create(CartPage.class); + var cartPage = app().createPage(CartPage.class); cartPage.applyCoupon("happybirthday"); cartPage.asserts().couponAppliedSuccessfully(); cartPage.increaseProductQuantity(3); @@ -115,7 +115,7 @@ public void purchaseSaturnVWithoutFacade() { purchaseInfo.setZip("10115"); purchaseInfo.setPhone("+498888999281"); - var checkoutPage = app().create(CheckoutPage.class); + var checkoutPage = app().createPage(CheckoutPage.class); checkoutPage.fillBillingInfo(purchaseInfo); checkoutPage.asserts().orderReceived(); } diff --git a/bellatrix.web/pom.xml b/bellatrix.web/pom.xml index 89374e44..c6ffc9a6 100644 --- a/bellatrix.web/pom.xml +++ b/bellatrix.web/pom.xml @@ -60,11 +60,6 @@ 2.15.3 compile - - io.github.bonigarcia - webdrivermanager - 5.3.2 - com.fasterxml.jackson.core jackson-annotations diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/CheckBox.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/CheckBox.java index 54d35aa1..0e3ba25c 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/CheckBox.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/CheckBox.java @@ -33,6 +33,15 @@ public void check() { defaultCheck(CHECKING, CHECKED); } + public void check(boolean shouldCheck) { + if (shouldCheck) { + check(); + } + else { + uncheck(); + } + } + public void uncheck() { defaultUncheck(UNCHECKING, UNCHECKED); } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/Image.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/Image.java index 2a00f9f3..a2622696 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/Image.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/Image.java @@ -13,14 +13,21 @@ package solutions.bellatrix.web.components; +import solutions.bellatrix.core.plugins.EventListener; import solutions.bellatrix.web.components.contracts.*; public class Image extends WebComponent implements ComponentSrc, ComponentHeight, ComponentWidth, ComponentLongDesc, ComponentAlt, ComponentSrcSet, ComponentSizes { + public final static EventListener CLICKING = new EventListener<>(); + public final static EventListener CLICKED = new EventListener<>(); @Override public Class getComponentClass() { return getClass(); } + public void click() { + defaultClick(CLICKING, CLICKED); + } + @Override public String getAlt() { return defaultGetAltAttribute(); diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/TextInput.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/TextInput.java index e0b1a087..67a6c75d 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/TextInput.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/TextInput.java @@ -13,6 +13,7 @@ package solutions.bellatrix.web.components; +import org.openqa.selenium.Keys; import solutions.bellatrix.core.plugins.EventListener; import solutions.bellatrix.web.components.contracts.*; 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 186a4046..4e0b3afd 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 @@ -29,7 +29,9 @@ import solutions.bellatrix.core.utilities.InstanceFactory; import solutions.bellatrix.core.utilities.Log; import solutions.bellatrix.web.components.contracts.Component; +import solutions.bellatrix.web.components.contracts.ComponentStyle; import solutions.bellatrix.web.components.contracts.ComponentVisible; +import solutions.bellatrix.web.components.enums.ScrollPosition; import solutions.bellatrix.web.configuration.WebSettings; import solutions.bellatrix.web.findstrategies.*; import solutions.bellatrix.web.infrastructure.Browser; @@ -51,7 +53,7 @@ import static org.apache.commons.text.StringEscapeUtils.unescapeHtml4; -public class WebComponent extends LayoutComponentValidationsBuilder implements Component, ComponentVisible { +public class WebComponent extends LayoutComponentValidationsBuilder implements Component, ComponentVisible, ComponentStyle { public final static EventListener HOVERING = new EventListener<>(); public final static EventListener HOVERED = new EventListener<>(); public final static EventListener FOCUSING = new EventListener<>(); @@ -106,7 +108,15 @@ public void waitToBe() { } public void scrollToVisible() { - scrollToVisible(getWrappedElement(), false); + scrollToVisible(getWrappedElement(), false, ScrollPosition.CENTER); + } + + public void scrollToTop() { + scrollToVisible(getWrappedElement(), false, ScrollPosition.START); + } + + public void scrollToBottom() { + scrollToVisible(getWrappedElement(), false, ScrollPosition.END); } public void setAttribute(String name, String value) { @@ -1036,19 +1046,20 @@ private void addArtificialDelay() { private void scrollToMakeElementVisible(WebElement wrappedElement) { // createBy default scroll down to make the element visible. if (webSettings.getAutomaticallyScrollToVisible()) { - scrollToVisible(wrappedElement, false); + scrollToVisible(wrappedElement, false, ScrollPosition.CENTER); } } - private void scrollToVisible(WebElement wrappedElement, boolean shouldWait) { + + private void scrollToVisible(WebElement wrappedElement, boolean shouldWait, ScrollPosition scrollPosition) { SCROLLING_TO_VISIBLE.broadcast(new ComponentActionEventArgs(this)); try { - javaScriptService.execute("arguments[0].scrollIntoView(true);", wrappedElement); + javaScriptService.execute("arguments[0].scrollIntoView({ block: \"" + scrollPosition.getValue() + "\", behavior: \"instant\", inline: \"nearest\" });", wrappedElement); if (shouldWait) { Thread.sleep(500); toExist().waitToBe(); } - } catch (ElementNotInteractableException | InterruptedException ex) { + } catch (ElementNotInteractableException | InterruptedException | ScriptTimeoutException ex) { DebugInformation.printStackTrace(ex); } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentBackgroundColor.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentBackgroundColor.java new file mode 100644 index 00000000..7d6b06aa --- /dev/null +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentBackgroundColor.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 Automate The Planet Ltd. + * Author: Teodor Nikolov + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package solutions.bellatrix.web.components.contracts; + +import org.openqa.selenium.support.Colors; +import solutions.bellatrix.core.utilities.SingletonFactory; +import solutions.bellatrix.web.components.WebComponent; +import solutions.bellatrix.web.services.JavaScriptService; +import solutions.bellatrix.web.validations.ComponentValidator; + +import java.lang.reflect.Method; +import java.util.function.Supplier; + +public interface ComponentBackgroundColor extends Component { + + default String getBackgroundColor(){ + var script = "return arguments[0].style.background"; + + return new JavaScriptService().execute(script, (WebComponent)this); + } + + default void validateBackgroundColor(Colors expectedColor) { + try { + Method method = ComponentValidator.class.getDeclaredMethod("defaultValidateAttributeIs", WebComponent.class, Supplier.class, String.class, String.class); + method.invoke(SingletonFactory.getInstance(ComponentValidator.class), (WebComponent) this, (Supplier)this::getBackgroundColor, expectedColor.getColorValue().getColor().getRGB(), String.format("expected color should be %s", expectedColor)); + } catch (Exception e) { + throw new RuntimeException(e.getCause()); + } + } +} diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentChecked.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentChecked.java index e4aa64b4..b196a074 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentChecked.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentChecked.java @@ -13,24 +13,25 @@ package solutions.bellatrix.web.components.contracts; -import lombok.SneakyThrows; -import solutions.bellatrix.core.utilities.SingletonFactory; import solutions.bellatrix.web.components.WebComponent; import solutions.bellatrix.web.validations.ComponentValidator; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.function.BooleanSupplier; - public interface ComponentChecked extends Component { boolean isChecked(); - @SneakyThrows default void validateIsChecked() { ComponentValidator.defaultValidateAttributeTrue((WebComponent)this, this::isChecked, "checked"); } - @SneakyThrows + default void validateIsChecked(boolean value) { + if (value){ + validateIsChecked(); + } + else { + validateIsUnchecked(); + } + } + default void validateIsUnchecked() { ComponentValidator.defaultValidateAttributeTrue((WebComponent)this, () -> !isChecked(), "unchecked"); } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentDisabled.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentDisabled.java index 46e6db0b..7b9e04ce 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentDisabled.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentDisabled.java @@ -14,14 +14,9 @@ package solutions.bellatrix.web.components.contracts; import lombok.SneakyThrows; -import solutions.bellatrix.core.utilities.SingletonFactory; import solutions.bellatrix.web.components.WebComponent; import solutions.bellatrix.web.validations.ComponentValidator; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.function.BooleanSupplier; - public interface ComponentDisabled extends Component { boolean isDisabled(); diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentStyle.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentStyle.java index 4d17cfaa..f5280cf1 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentStyle.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/contracts/ComponentStyle.java @@ -16,6 +16,8 @@ import lombok.SneakyThrows; import solutions.bellatrix.core.utilities.SingletonFactory; import solutions.bellatrix.web.components.WebComponent; +import solutions.bellatrix.web.components.enums.CssStyle; +import solutions.bellatrix.web.services.JavaScriptService; import solutions.bellatrix.web.validations.ComponentValidator; import java.lang.reflect.InvocationTargetException; @@ -49,4 +51,21 @@ default void validateStyleContains(String value) { default void validateStyleNotContains(String value) { ComponentValidator.defaultValidateAttributeNotContains((WebComponent)this, this::getStyle, value, "style"); } + + default String getStyle(CssStyle style) { + var script = String.format("return window.getComputedStyle(arguments[0],null).getPropertyValue('%s');", style); + var result = new JavaScriptService().execute(script, (WebComponent) this); + + return result; + } + + @SneakyThrows + default void validateStyle(CssStyle style, String expectedValue) { + try { + Method method = ComponentValidator.class.getDeclaredMethod("defaultValidateAttributeIs", WebComponent.class, Supplier.class, java.lang.String.class, java.lang.String.class); + method.invoke(SingletonFactory.getInstance(ComponentValidator.class), (WebComponent) this, (Supplier) () -> this.getStyle(style), expectedValue, java.lang.String.format("expected color should be %s", expectedValue)); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/enums/CssStyle.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/enums/CssStyle.java new file mode 100644 index 00000000..7f5cf8c9 --- /dev/null +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/enums/CssStyle.java @@ -0,0 +1,17 @@ +package solutions.bellatrix.web.components.enums; + +public enum CssStyle { + BACKGROUND_COLOR("background-color"), + COLOR("color"); + + private final String style; + + CssStyle(String style) { + this.style = style; + } + + @Override + public String toString() { + return this.style; + } +} diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/enums/ScrollPosition.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/enums/ScrollPosition.java new file mode 100644 index 00000000..bf8722fc --- /dev/null +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/enums/ScrollPosition.java @@ -0,0 +1,21 @@ +package solutions.bellatrix.web.components.enums; + +import lombok.Getter; + +@Getter +public enum ScrollPosition { + CENTER("center"), + START("start"), + END("end"); + + private final String value; + + ScrollPosition(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } +} diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/listeners/BddConsoleLogging.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/listeners/BddConsoleLogging.java index f77c8a13..0543f7d0 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/listeners/BddConsoleLogging.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/listeners/BddConsoleLogging.java @@ -49,7 +49,7 @@ public void addListener() { WeekInput.SETTING_WEEK.addListener((x) -> Log.info("setting '%s' in %s", x.getActionValue(), x.getComponent().getComponentName())); WebComponent.HOVERING.addListener((x) -> Log.info("hovering %s", x.getComponent().getComponentName())); WebComponent.FOCUSING.addListener((x) -> Log.info("focusing %s", x.getComponent().getComponentName())); - WebComponent.SCROLLING_TO_VISIBLE.addListener((x) -> Log.info("scrolling to %s", x.getComponent().getComponentName())); +// WebComponent.SCROLLING_TO_VISIBLE.addListener((x) -> Log.info("scrolling to %s", x.getComponent().getComponentName())); WebComponent.SETTING_ATTRIBUTE.addListener((x) -> Log.info("setting %s to '%s' in %s", x.getActionValue(), x.getMessage(), x.getComponent().getComponentName())); ComponentValidator.VALIDATING_ATTRIBUTE.addListener((x) -> Log.info(x.getMessage())); isBddLoggingTurnedOn = true; diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/listeners/BddToastNotificationsLogging.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/listeners/BddToastNotificationsLogging.java index bc2bf5d1..474b55a0 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/components/listeners/BddToastNotificationsLogging.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/components/listeners/BddToastNotificationsLogging.java @@ -52,7 +52,7 @@ public void addListener() { WeekInput.SETTING_WEEK.addListener((x) -> new BrowserService().injectInfoNotificationToast("setting '%s' in %s", x.getActionValue(), x.getComponent().getComponentName())); WebComponent.HOVERING.addListener((x) -> new BrowserService().injectInfoNotificationToast("hovering %s", x.getComponent().getComponentName())); WebComponent.FOCUSING.addListener((x) -> new BrowserService().injectInfoNotificationToast("focusing %s", x.getComponent().getComponentName())); - WebComponent.SCROLLING_TO_VISIBLE.addListener((x) -> new BrowserService().injectInfoNotificationToast("scrolling to %s", x.getComponent().getComponentName())); +// WebComponent.SCROLLING_TO_VISIBLE.addListener((x) -> new BrowserService().injectInfoNotificationToast("scrolling to %s", x.getComponent().getComponentName())); WebComponent.SETTING_ATTRIBUTE.addListener((x) -> new BrowserService().injectInfoNotificationToast("setting %s to '%s' in %s", x.getActionValue(), x.getMessage(), x.getComponent().getComponentName())); ComponentValidator.VALIDATING_ATTRIBUTE.addListener((x) -> new BrowserService().injectInfoNotificationToast(x.getMessage())); isBddLoggingTurnedOn = true; diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/configuration/UrlDeterminer.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/configuration/UrlDeterminer.java index 010cab76..be675da0 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/configuration/UrlDeterminer.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/configuration/UrlDeterminer.java @@ -13,7 +13,7 @@ package solutions.bellatrix.web.configuration; -import org.apache.hc.core5.net.URIBuilder; +import org.apache.http.client.utils.URIBuilder; import solutions.bellatrix.core.configuration.ConfigurationService; import java.net.URI; diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/configuration/WebSettings.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/configuration/WebSettings.java index 5e488967..596561ee 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/configuration/WebSettings.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/configuration/WebSettings.java @@ -27,6 +27,7 @@ import lombok.Getter; import lombok.Setter; +import java.util.ArrayList; import java.util.List; public class WebSettings { @@ -34,7 +35,8 @@ public class WebSettings { @Getter @Setter private String executionType; @Getter @Setter private String defaultLifeCycle; @Getter @Setter private String defaultBrowser; - + @Getter @Setter private Integer defaultBrowserWidth = 0; + @Getter @Setter private Integer defaultBrowserHeight = 0; @Getter @Setter private List gridSettings; @Getter @Setter private int artificialDelayBeforeAction; @@ -48,6 +50,8 @@ public class WebSettings { @Getter @Setter private Boolean toastNotificationBddLogging; @Getter @Setter private long notificationToastTimeout; + @Getter @Setter private ArrayList consoleErrorsWhitelist; + @Getter @Setter private Boolean screenshotsOnFailEnabled; @Getter @Setter private String screenshotsSaveLocation; diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/Browser.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/Browser.java index 5cecd77a..2ff16eb7 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/Browser.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/Browser.java @@ -17,13 +17,15 @@ public enum Browser { CHROME("chrome"), - CHROME_HEADLESS("chrome"), + CHROME_HEADLESS("chrome_headless"), + CHROME_MOBILE("chrome_mobile"), FIREFOX("firefox"), - FIREFOX_HEADLESS("firefox"), + FIREFOX_HEADLESS("firefox_headless"), EDGE("edge"), // EDGE_HEADLESS("edge"), // Unsupported by Selenium 3, Selenium 4 has support OPERA("opera"), SAFARI("safari"), + NOT_SET("not_set"), INTERNET_EXPLORER("ie"); private final String value; 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 663704ab..80205ff9 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 @@ -21,6 +21,7 @@ public class BrowserConfiguration { @Setter @Getter private Browser browser; + @Setter @Getter private DeviceName deviceName; @Setter @Getter private Lifecycle lifecycle; @Setter @Getter private int height; @Setter @Getter private int width; @@ -39,6 +40,21 @@ public BrowserConfiguration(Browser browser, Lifecycle browserBehavior) { driverOptions = new HashMap<>(); } + public BrowserConfiguration(Browser browser, DeviceName deviceName, Lifecycle browserBehavior) { + this.deviceName = deviceName; + this.browser = browser; + this.lifecycle = browserBehavior; + driverOptions = new HashMap<>(); + } + + public BrowserConfiguration(Browser browser, Lifecycle browserBehavior, Integer browserWidth, Integer browserHeight) { + this.browser = browser; + this.lifecycle = browserBehavior; + this.width = browserWidth; + this.height = browserHeight; + driverOptions = new HashMap<>(); + } + public BrowserConfiguration(Browser browser, Lifecycle browserBehavior, String testName) { this.browser = browser; this.lifecycle = browserBehavior; @@ -46,6 +62,14 @@ public BrowserConfiguration(Browser browser, Lifecycle browserBehavior, String t driverOptions = new HashMap<>(); } + public BrowserConfiguration(DeviceName deviceName, Lifecycle browserBehavior, String testName) { + this.browser = Browser.CHROME_MOBILE; + this.lifecycle = browserBehavior; + this.testName = testName; + this.deviceName = deviceName; + driverOptions = new HashMap<>(); + } + @Override public boolean equals(Object obj) { if (!(obj instanceof BrowserConfiguration)) 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 ab8b1594..e36a2211 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 @@ -17,6 +17,7 @@ import solutions.bellatrix.core.plugins.Plugin; import solutions.bellatrix.core.plugins.TestResult; import solutions.bellatrix.core.utilities.DebugInformation; +import solutions.bellatrix.core.utilities.SecretsResolver; import solutions.bellatrix.web.configuration.WebSettings; import java.lang.reflect.Method; @@ -31,13 +32,13 @@ public class BrowserLifecyclePlugin extends Plugin { static { CURRENT_BROWSER_CONFIGURATION = new ThreadLocal<>(); PREVIOUS_BROWSER_CONFIGURATION = new ThreadLocal<>(); - IS_BROWSER_STARTED_DURING_PRE_BEFORE_CLASS = new ThreadLocal<>(); - IS_BROWSER_STARTED_CORRECTLY = new ThreadLocal<>(); + IS_BROWSER_STARTED_DURING_PRE_BEFORE_CLASS = ThreadLocal.withInitial(() -> false); + IS_BROWSER_STARTED_CORRECTLY = ThreadLocal.withInitial(() -> false); } @Override public void preBeforeClass(Class type) { - if (ConfigurationService.get(WebSettings.class).getExecutionType() == "regular") { + if (Objects.equals(ConfigurationService.get(WebSettings.class).getExecutionType(), "regular")) { CURRENT_BROWSER_CONFIGURATION.set(getExecutionBrowserClassLevel(type)); if (shouldRestartBrowser()) { shutdownBrowser(); @@ -64,14 +65,10 @@ public void postAfterClass(Class type) { @Override public void preBeforeTest(TestResult testResult, Method memberInfo) { CURRENT_BROWSER_CONFIGURATION.set(getBrowserConfiguration(memberInfo)); - - if (!IS_BROWSER_STARTED_DURING_PRE_BEFORE_CLASS.get()) { - if (shouldRestartBrowser()) { - startBrowser(); - } + if (shouldRestartBrowser()) { + shutdownBrowser(); + startBrowser(); } - - IS_BROWSER_STARTED_DURING_PRE_BEFORE_CLASS.set(false); } @Override @@ -96,7 +93,7 @@ public void postAfterTest(TestResult testResult, Method memberInfo, Throwable fa private void shutdownBrowser() { DriverService.close(); - PREVIOUS_BROWSER_CONFIGURATION.set(null); + PREVIOUS_BROWSER_CONFIGURATION.remove(); } private void startBrowser() { @@ -147,17 +144,27 @@ private BrowserConfiguration getExecutionBrowserMethodLevel(Method memberInfo) { return null; } - return new BrowserConfiguration(executionBrowserAnnotation.browser(), executionBrowserAnnotation.lifecycle()); + return new BrowserConfiguration(executionBrowserAnnotation.browser(), executionBrowserAnnotation.deviceName(), executionBrowserAnnotation.lifecycle()); } private BrowserConfiguration getExecutionBrowserClassLevel(Class type) { var executionBrowserAnnotation = (ExecutionBrowser)type.getDeclaredAnnotation(ExecutionBrowser.class); - if (executionBrowserAnnotation == null) { - var defaultBrowser = Browser.fromText(ConfigurationService.get(WebSettings.class).getDefaultBrowser()); - var defaultLifecycle = Lifecycle.fromText(ConfigurationService.get(WebSettings.class).getDefaultLifeCycle()); - return new BrowserConfiguration(defaultBrowser, defaultLifecycle); - } - return new BrowserConfiguration(executionBrowserAnnotation.browser(), executionBrowserAnnotation.lifecycle()); + var defaultBrowser = Browser.fromText(SecretsResolver.getSecret(ConfigurationService.get(WebSettings.class).getDefaultBrowser())); + var defaultLifecycle = Lifecycle.fromText(ConfigurationService.get(WebSettings.class).getDefaultLifeCycle()); + var defaultWidth = ConfigurationService.get(WebSettings.class).getDefaultBrowserWidth(); + var defaultHeight = ConfigurationService.get(WebSettings.class).getDefaultBrowserHeight(); + + var finalBrowser = executionBrowserAnnotation.browser() != Browser.NOT_SET && executionBrowserAnnotation.browser() != defaultBrowser ? executionBrowserAnnotation.browser() : defaultBrowser; + var finalLifecycle = executionBrowserAnnotation.lifecycle() != defaultLifecycle ? executionBrowserAnnotation.lifecycle() : defaultLifecycle; + var finalWidth = executionBrowserAnnotation.width() != 0 ? executionBrowserAnnotation.width() : defaultWidth; + var finalHeight = executionBrowserAnnotation.height() != 0 ? executionBrowserAnnotation.height() : defaultHeight; + + if (executionBrowserAnnotation.browser() == Browser.CHROME_MOBILE) { + return new BrowserConfiguration(executionBrowserAnnotation.deviceName(), finalLifecycle, type.getName()); + } + else { + return new BrowserConfiguration(finalBrowser, finalLifecycle, finalWidth, finalHeight); + } } } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/DeviceName.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/DeviceName.java new file mode 100644 index 00000000..20f4cd2d --- /dev/null +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/DeviceName.java @@ -0,0 +1,54 @@ +/* + * Copyright 2022 Automate The Planet Ltd. + * Author: Anton Angelov + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package solutions.bellatrix.web.infrastructure; + +import lombok.Getter; + +@Getter +public enum DeviceName { + NOT_SET(), + IPHONE_13_PRO_MOBILE("iPhone 13 Pro", 375, 667, true, 2, true, true, "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A366 Safari/600.1.4"), + IPHONE_SE_MOBILE("iPhone SE", 750, 1334, true, 2, true, true, "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A366 Safari/600.1.4"), + IPHONE_X_MOBILE("iPhone X", 375, 667, true, 3, true, true, "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1"), + NEXUS_7_TABLET("Nexus 7", 600, 960, true, 2, true, true, "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 7 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.136 Safari/537.36"), + IPAD("Apple iPad", 768, 1024, true, 2, true, true, "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9B176 Safari/7534.48.3"); + + private final String name; + private final int width; + private final int height; + private final boolean isMobile; + private final int scaleFactor; + private final boolean supportsConsoleLogs; + private final boolean supportsFullPage; + private final String userAgent; + + DeviceName(String name, int width, int height, boolean isMobile, int scaleFactor, boolean supportsConsoleLogs, boolean supportsFullPage) { + this(name, width, height, isMobile, scaleFactor, supportsConsoleLogs, supportsFullPage, null); + } + + DeviceName() { + this(null, 0, 0, false, 0, false, false, null); + } + + DeviceName(String name, int width, int height, boolean isMobile, int scaleFactor, boolean supportsConsoleLogs, boolean supportsFullPage, String userAgent) { + this.name = name; + this.width = width; + this.height = height; + this.isMobile = isMobile; + this.scaleFactor = scaleFactor; + this.supportsConsoleLogs = supportsConsoleLogs; + this.supportsFullPage = supportsFullPage; + this.userAgent = userAgent; + } +} diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/DriverService.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/DriverService.java index b821309e..d21ebea1 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/DriverService.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/DriverService.java @@ -13,7 +13,6 @@ package solutions.bellatrix.web.infrastructure; -import io.github.bonigarcia.wdm.WebDriverManager; import net.lightbody.bmp.client.ClientUtil; import org.openqa.selenium.*; import org.openqa.selenium.chrome.ChromeDriver; @@ -24,6 +23,7 @@ import org.openqa.selenium.firefox.FirefoxOptions; import org.openqa.selenium.ie.InternetExplorerDriver; import org.openqa.selenium.ie.InternetExplorerOptions; +import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.safari.SafariDriver; @@ -42,10 +42,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Properties; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -106,9 +103,16 @@ public static WebDriver start(BrowserConfiguration configuration) { } driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(ConfigurationService.get(WebSettings.class).getTimeoutSettings().getPageLoadTimeout())); - driver.manage().timeouts().setScriptTimeout(Duration.ofSeconds(ConfigurationService.get(WebSettings.class).getTimeoutSettings().getScriptTimeout())); - driver.manage().window().maximize(); - changeWindowSize(driver); + driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(ConfigurationService.get(WebSettings.class).getTimeoutSettings().getScriptTimeout())); + + if(getBrowserConfiguration().getHeight() != 0 && getBrowserConfiguration().getWidth() != 0) { + changeWindowSize(driver); + } + else { + driver.manage().window().maximize(); + } + + Log.info(String.format("Window resized to dimensions: %s", driver.manage().window().getSize().toString())); WRAPPED_DRIVER.set(driver); return driver; @@ -160,7 +164,6 @@ private static WebDriver initializeDriverCloudGridMode(GridSettings gridSettings var url = getUrl(gridSettings.getUrl()); driver = new RemoteWebDriver(new URI(url).toURL(), caps); } catch (Exception e) { - ; DebugInformation.printStackTrace(e); } @@ -235,35 +238,54 @@ private static WebDriver initializeDriverRegularMode() { if (shouldCaptureHttpTraffic) { ProxyServer.init(); proxyConfig = ClientUtil.createSeleniumProxy(ProxyServer.get()); + ProxyServer.newHar(); } - switch (BROWSER_CONFIGURATION.get().getBrowser()) { case CHROME -> { - //WebDriverManager.chromedriver().setup(); var chromeOptions = new ChromeOptions(); addDriverOptions(chromeOptions); - chromeOptions.addArguments("--log-level=3", "--remote-allow-origins=*"); + addDriverCapabilities(chromeOptions); + chromeOptions.addArguments("--log-level=3","--remote-allow-origins=*"); chromeOptions.setAcceptInsecureCerts(true); + chromeOptions.setCapability(CapabilityType.UNHANDLED_PROMPT_BEHAVIOUR, UnexpectedAlertBehaviour.ACCEPT); System.setProperty("webdriver.chrome.silentOutput", "true"); - if (shouldCaptureHttpTraffic) chromeOptions.setProxy(proxyConfig); + if (shouldCaptureHttpTraffic) { + chromeOptions.setProxy(proxyConfig); + } driver = new ChromeDriver(chromeOptions); } case CHROME_HEADLESS -> { - WebDriverManager.chromedriver().setup(); var chromeHeadlessOptions = new ChromeOptions(); addDriverOptions(chromeHeadlessOptions); chromeHeadlessOptions.setAcceptInsecureCerts(true); - chromeHeadlessOptions.addArguments("--log-level=3"); - chromeHeadlessOptions.addArguments("--headless"); + chromeHeadlessOptions.addArguments("--log-level=3","--remote-allow-origins=*"); + chromeHeadlessOptions.setCapability(CapabilityType.UNHANDLED_PROMPT_BEHAVIOUR, UnexpectedAlertBehaviour.ACCEPT); + chromeHeadlessOptions.addArguments("--headless=old"); System.setProperty("webdriver.chrome.silentOutput", "true"); if (shouldCaptureHttpTraffic) chromeHeadlessOptions.setProxy(proxyConfig); driver = new ChromeDriver(chromeHeadlessOptions); } + case CHROME_MOBILE -> { + var chromeHeadlessOptions = new ChromeOptions(); + addDriverOptions(chromeHeadlessOptions); + chromeHeadlessOptions.setAcceptInsecureCerts(true); + chromeHeadlessOptions.addArguments("--log-level=3","--remote-allow-origins=*"); + chromeHeadlessOptions.setCapability(CapabilityType.UNHANDLED_PROMPT_BEHAVIOUR, UnexpectedAlertBehaviour.ACCEPT); + + var deviceNameOption = new HashMap(); + deviceNameOption.put("deviceName", BROWSER_CONFIGURATION.get().getDeviceName().getName()); + + chromeHeadlessOptions.setExperimentalOption("mobileEmulation", deviceNameOption); + System.setProperty("webdriver.chrome.silentOutput", "true"); + if (shouldCaptureHttpTraffic) chromeHeadlessOptions.setProxy(proxyConfig); + + driver = new TouchableWebDriver(chromeHeadlessOptions); + } + case FIREFOX -> { - WebDriverManager.firefoxdriver().setup(); var firefoxOptions = new FirefoxOptions(); addDriverOptions(firefoxOptions); firefoxOptions.setAcceptInsecureCerts(true); @@ -271,7 +293,6 @@ private static WebDriver initializeDriverRegularMode() { driver = new FirefoxDriver(firefoxOptions); } case FIREFOX_HEADLESS -> { - WebDriverManager.firefoxdriver().setup(); var firefoxHeadlessOptions = new FirefoxOptions(); addDriverOptions(firefoxHeadlessOptions); firefoxHeadlessOptions.setAcceptInsecureCerts(true); @@ -280,8 +301,6 @@ private static WebDriver initializeDriverRegularMode() { driver = new FirefoxDriver(firefoxHeadlessOptions); } case EDGE -> { - - WebDriverManager.edgedriver().setup(); var edgeOptions = new EdgeOptions(); addDriverOptions(edgeOptions); if (shouldCaptureHttpTraffic) edgeOptions.setProxy(proxyConfig); @@ -295,7 +314,6 @@ private static WebDriver initializeDriverRegularMode() { driver = new SafariDriver(safariOptions); } case INTERNET_EXPLORER -> { - WebDriverManager.iedriver().setup(); var internetExplorerOptions = new InternetExplorerOptions(); addDriverOptions(internetExplorerOptions); internetExplorerOptions.introduceFlakinessByIgnoringSecurityDomains().ignoreZoomSettings(); @@ -307,6 +325,25 @@ private static WebDriver initializeDriverRegularMode() { return driver; } + private static DesiredCapabilities addDriverCapabilities(ChromeOptions chromeOptions) { + DesiredCapabilities caps = new DesiredCapabilities(); + // INIT CHROME OPTIONS + Map prefs = new HashMap(); + Map profile = new HashMap(); + Map contentSettings = new HashMap(); + + // SET CHROME OPTIONS + // 0 - Default, 1 - Allow, 2 - Block + contentSettings.put("notifications", 1); + profile.put("managed_default_content_settings", contentSettings); + prefs.put("profile", profile); + chromeOptions.setExperimentalOption("prefs", prefs); + + // SET CAPABILITY + caps.setCapability(ChromeOptions.CAPABILITY, chromeOptions); + return caps; + } + private static void addGridOptions(HashMap options, GridSettings gridSettings) { Log.info("Add WebDriver Options:"); Log.info(""); @@ -382,9 +419,10 @@ private static void addDriverOptions(TOpti private static void changeWindowSize(WebDriver wrappedDriver) { try { if (getBrowserConfiguration().getHeight() != 0 && getBrowserConfiguration().getWidth() != 0) { - wrappedDriver.manage().window().setSize(new Dimension(getBrowserConfiguration().getHeight(), getBrowserConfiguration().getWidth())); + Log.info(String.format("Setting window size to %sx%s",getBrowserConfiguration().getWidth(), getBrowserConfiguration().getHeight())); + wrappedDriver.manage().window().setSize(new Dimension(getBrowserConfiguration().getWidth(), getBrowserConfiguration().getHeight())); } - } catch (Exception ignored) {} + } catch (Exception ex) { System.out.println("Error while resizing browser window: " + ex.getMessage());} } private static String getBuildName() { diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/ExecutionBrowser.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/ExecutionBrowser.java index 21466b86..f5e93e48 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/ExecutionBrowser.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/ExecutionBrowser.java @@ -23,10 +23,11 @@ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ExecutionBrowser { - Browser browser() default Browser.CHROME; - Lifecycle lifecycle() default Lifecycle.RESTART_EVERY_TIME; + Browser browser() default Browser.NOT_SET; + Lifecycle lifecycle(); int browserVersion() default 0; Platform platform() default Platform.ANY; int width() default 0; int height() default 0; + DeviceName deviceName() default DeviceName.NOT_SET; } \ 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 456522f6..3cf46682 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 @@ -14,26 +14,56 @@ 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 lombok.SneakyThrows; import net.lightbody.bmp.BrowserMobProxyServer; import net.lightbody.bmp.core.har.HarEntry; import net.lightbody.bmp.proxy.CaptureType; +import org.apache.http.HttpStatus; +import org.asynchttpclient.uri.Uri; +import org.openqa.selenium.TimeoutException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.remote.http.HttpMethod; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.opentest4j.AssertionFailedError; import org.testng.Assert; +import solutions.bellatrix.core.configuration.ConfigurationService; +import solutions.bellatrix.core.utilities.Log; +import solutions.bellatrix.web.configuration.WebSettings; import java.io.IOException; +import java.lang.reflect.Type; import java.net.ServerSocket; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class ProxyServer { private static final ThreadLocal PROXY_SERVER = ThreadLocal.withInitial(BrowserMobProxyServer::new); private static final ThreadLocal PORT = new ThreadLocal<>(); + private static final List successHttpStatusesList = Arrays.asList( + HttpStatus.SC_OK, + HttpStatus.SC_CREATED, + HttpStatus.SC_ACCEPTED, + HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION, + HttpStatus.SC_NO_CONTENT, + HttpStatus.SC_RESET_CONTENT, + HttpStatus.SC_UNPROCESSABLE_ENTITY, + HttpStatus.SC_PARTIAL_CONTENT, + HttpStatus.SC_MULTI_STATUS); @SneakyThrows public static int init() { + Log.info("Starting Proxy Service..."); int port = findFreePort(); + PROXY_SERVER.get().setTrustAllServers(true); PROXY_SERVER.get().start(port); - PROXY_SERVER.get().enableHarCaptureTypes(CaptureType.REQUEST_CONTENT, CaptureType.RESPONSE_CONTENT); + 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)); return port; } @@ -50,11 +80,13 @@ public static void newHar() { } public static void close() { + Log.info("Stopping Proxy Service..."); BrowserMobProxyServer proxyServer = PROXY_SERVER.get(); if (proxyServer != null) { proxyServer.stop(); PROXY_SERVER.remove(); PORT.remove(); + Log.info("Proxy Service Stopped."); } } @@ -74,8 +106,90 @@ public static void assertNoErrorCodes() { public static void assertRequestMade(String url) { var harEntries = PROXY_SERVER.get().getHar().getLog().getEntries(); var areRequestsMade = harEntries.stream().anyMatch(r -> r.getRequest().getUrl().contains(url)); + String simiarRequestsString = getSimilarRequestsString(url, harEntries); + Assert.assertTrue(areRequestsMade, String.format("The expected url '%s' was not loaded! Similar requests: %s", url, simiarRequestsString)); + } + + public static void assertRequestNotMade(String url, HttpMethod httpMethod) { + var harEntries = PROXY_SERVER.get().getHar().getLog().getEntries(); + var areRequestsMade = harEntries.stream().anyMatch(r -> r.getRequest().getUrl().contains(url) && r.getRequest().getMethod().equals(httpMethod.toString())); + + Assert.assertFalse(areRequestsMade); + } + + public static void clearHistory() { + var oldHarCount = PROXY_SERVER.get().getHar().getLog().getEntries().stream().count(); + + PROXY_SERVER.get().newHar(); + Log.info(String.format("The proxy history with %s entries is cleared!", oldHarCount)); + } + + public static void waitForRequest(WebDriver driver, String requestPartialUrl, HttpMethod httpMethod, int additionalTimeoutInSeconds) { + long timeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitForAjaxTimeout(); + long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval(); + var webDriverWait = new WebDriverWait(driver, Duration.ofSeconds(timeout + additionalTimeoutInSeconds), Duration.ofSeconds(sleepInterval)); + + try { + webDriverWait.until(d -> { + var harEntries = PROXY_SERVER.get().getHar().getLog().getEntries(); + var areRequestsMade = harEntries.stream().anyMatch(r -> r.getRequest().getUrl().contains(requestPartialUrl) && r.getRequest().getMethod().equals(httpMethod.toString())); + + return areRequestsMade; + }); + } + catch (TimeoutException exception){ + Log.error(String.format("The expected request with URL '%s' is not loaded!", requestPartialUrl)); + } + } + + public static void waitForResponse(WebDriver driver, String requestPartialUrl, HttpMethod httpMethod, int additionalTimeoutInSeconds) { + long timeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitForAjaxTimeout(); + long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval(); + var webDriverWait = new WebDriverWait(driver, Duration.ofSeconds(timeout + additionalTimeoutInSeconds), Duration.ofSeconds(sleepInterval)); + List allHarEntries = new ArrayList<>(); + try { + webDriverWait.until(d -> { + var harEntries = PROXY_SERVER.get().getHar().getLog().getEntries(); + var isResponseReceived = harEntries.stream().anyMatch( + r -> r.getRequest().getUrl().contains(requestPartialUrl) + && r.getRequest().getMethod().equals(httpMethod.toString()) + && successHttpStatusesList.contains(r.getResponse().getStatus()) + && (httpMethod.equals(HttpMethod.DELETE)? true : (r.getResponse().getContent().getText() != null && !r.getResponse().getContent().getText().isEmpty())) + ); + allHarEntries.clear(); + allHarEntries.addAll(harEntries); + return isResponseReceived; + }); + } + catch (TimeoutException exception){ + String allUrlsString = getSimilarRequestsString(requestPartialUrl, allHarEntries); - Assert.assertTrue(areRequestsMade); + 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)); + } + } + + private static String getSimilarRequestsString(String requestPartialUrl, List allHarEntries) { + try{ + ArrayList allUrls = new ArrayList<>(); + allHarEntries.stream().forEach(e -> { + Uri uri = Uri.create(requestPartialUrl); + if(e.getRequest().getUrl().contains(uri.getHost()) || e.getRequest().getUrl().contains(requestPartialUrl)){ + allUrls.add(String.format("[%s]%s[%s]", e.getRequest().getMethod(), e.getRequest().getUrl(), e.getResponse().getStatusText())); + } + }); + String allUrlsString = ""; + for (String url: + allUrls) { + allUrlsString += url; + allUrlsString += "\r\n"; + } + return allUrlsString; + } + catch (Exception ex){ + ArrayList allUrls = new ArrayList<>(); + allHarEntries.forEach(e -> allUrls.add(e.getRequest().getUrl())); + return allUrls.toString(); + } } public static void assertNoLargeImagesRequested() { @@ -115,7 +229,7 @@ public static T getLastResponse(Class responseModelClass) { return getCapturedEntries().stream() .map(HarEntry::getResponse) .filter(response -> response.getContent() != null) - .map(response -> new Gson().fromJson(response.getContent().getText(), responseModelClass)) + .map(response -> new Gson().fromJson(getDataObject(response.getContent().getText()), responseModelClass)) .reduce((first, second) -> second) .orElse(null); } @@ -130,32 +244,131 @@ public static T getRequestByIndex(int index, Class requestModelClass) { public static T getResponseByIndex(int index, Class responseModelClass) { var harEntry = getCapturedEntries().get(index); String json = harEntry.getResponse().getContent().getText(); - return new Gson().fromJson(json, responseModelClass); + return new Gson().fromJson(getDataObject(json), responseModelClass); } public static T getRequestByUrl(String url, String httpMethod, Class requestModelClass) { var harEntries = getCapturedEntries(); var harEntry = harEntries.stream() - .filter(r -> r.getRequest().getUrl().equals(url) && r.getRequest().getMethod().equals(httpMethod)) + .filter(r -> r.getRequest().getUrl().contains(url) && r.getRequest().getMethod().equals(httpMethod)) .findFirst() .orElse(null); if (harEntry == null) { return null; } String json = harEntry.getRequest().getPostData().getText(); - return new Gson().fromJson(json, requestModelClass); + try { + return new Gson().fromJson(json, requestModelClass); + } + catch (Exception e) { + throw new RuntimeException("Error occurred while converting json to model. Json was: %s".formatted(json), e); + } + } + + public static T getRequestByUrl(String url, String httpMethod, Type modelType) { + var harEntries = getCapturedEntries(); + var harEntry = harEntries.stream() + .filter(r -> r.getRequest().getUrl().contains(url) && r.getRequest().getMethod().equals(httpMethod)) + .findFirst() + .orElse(null); + if (harEntry == null) { + return null; + } + String json = harEntry.getRequest().getPostData().getText(); + try { + return new Gson().fromJson(json, modelType); + } + catch (Exception e) { + throw new RuntimeException("Error occurred while converting json to model. Json was: %s".formatted(json), e); + } } public static T getResponseByUrl(String url, String httpMethod, Class responseModelClass) { var harEntries = getCapturedEntries(); var harEntry = harEntries.stream() - .filter(r -> r.getRequest().getUrl().equals(url) && r.getRequest().getMethod().equals(httpMethod)) + .filter(r -> r.getRequest().getUrl().contains(url) && r.getRequest().getMethod().equals(httpMethod)) .findFirst() .orElse(null); if (harEntry == null) { + System.out.println("There is no match!"); return null; } String json = harEntry.getResponse().getContent().getText(); - return new Gson().fromJson(json, responseModelClass); + try { + return new Gson().fromJson(getDataObject(json), responseModelClass); + } + catch (Exception ex){ + throw new AssertionFailedError("Cannot get JSON body from the string: " + json + ". Error was: " + ex.getMessage()); + } + } + + public static T getResponseByUrl(String url, String httpMethod, Type responseModelType) { + var harEntries = getCapturedEntries(); + var harEntry = harEntries.stream() + .filter(r -> r.getRequest().getUrl().contains(url) && r.getRequest().getMethod().equals(httpMethod)) + .findFirst() + .orElse(null); + if (harEntry == null) { + System.out.println("There is no match!"); + return null; + } + String json = harEntry.getResponse().getContent().getText(); + return new Gson().fromJson(getDataObject(json), responseModelType); + } + + public static void blockRequestByUrl(String url, HttpMethod httpMethod) { + PROXY_SERVER.get().blacklistRequests(url,407, httpMethod.toString()); + } + + public static void blockRequestByUrl(String url) { + PROXY_SERVER.get().blacklistRequests(url,407); + } + + public static void clearblockRequestList() { + PROXY_SERVER.get().clearBlacklist(); + } + + private static String getDataObject(String jsonString) { + JsonParser parser = new JsonParser(); + JsonObject jsonObject = null; + JsonArray jsonArray = null; + boolean isObject = false; + + try { + jsonObject = (JsonObject) parser.parse(jsonString); + isObject = true; + } + catch (NullPointerException nullEx){ + throw new RuntimeException("JSON data is null. " + nullEx.getMessage()); + } + catch (Exception exception) { + jsonArray = (JsonArray) parser.parse(jsonString); + } + + JsonObject dataObject = null; + JsonArray dataArray = null; + if (isObject) { + if (jsonObject.has("data")){ + if (jsonObject.get("data").isJsonObject()){ + dataObject = jsonObject.getAsJsonObject("data"); + } else { + dataArray = jsonObject.getAsJsonArray("data"); + } + } else { + dataObject = jsonObject.getAsJsonObject(); + } + } else { + dataArray = jsonArray.getAsJsonArray(); + } + + if (dataObject != null){ + return dataObject.toString(); + } + else if (dataArray != null) { + return dataArray.toString(); + } + else { + return jsonString; + } } } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/TouchableWebDriver.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/TouchableWebDriver.java new file mode 100644 index 00000000..403d6580 --- /dev/null +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/TouchableWebDriver.java @@ -0,0 +1,79 @@ +package solutions.bellatrix.web.infrastructure; + +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.interactions.PointerInput; +import org.openqa.selenium.interactions.Sequence; + +import java.time.Duration; +import java.util.Arrays; + +public class TouchableWebDriver extends ChromeDriver implements WebDriver { + + public TouchableWebDriver(ChromeOptions options) { + super(options); + } + + public void triggerSwipeEvent(WebElement swipeFromElement) + { + PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); + Sequence tap = new Sequence(finger, 1); + + // trigger swipe event from the middle of the right edge if the element + tap.addAction(finger.createPointerMove(Duration.ofMillis(0), PointerInput.Origin.viewport(), swipeFromElement.getLocation().x + swipeFromElement.getSize().getWidth(), swipeFromElement.getLocation().y + (swipeFromElement.getSize().getHeight()/ 2 ))); + tap.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg())); + tap.addAction(finger.createPointerMove(Duration.ofMillis(100), PointerInput.Origin.viewport(), swipeFromElement.getLocation().x, swipeFromElement.getLocation().y + (swipeFromElement.getSize().getHeight()/ 2 ))); + tap.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg())); + this.perform(Arrays.asList(tap)); + } + + public void triggerTapEvent(WebElement swipeFromElement) + { + PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); + Sequence tap = new Sequence(finger, 1); + + int centerX = swipeFromElement.getLocation().x + swipeFromElement.getSize().getWidth() / 2; + int centerY = swipeFromElement.getLocation().y + swipeFromElement.getSize().getHeight() / 2; + + // Move to the center of the element and perform a tap + tap.addAction(finger.createPointerMove(Duration.ofMillis(0), PointerInput.Origin.viewport(), centerX, centerY)); + tap.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg())); + tap.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg())); + this.perform(Arrays.asList(tap)); + } +// +// public void triggerTapEvent(WebElement tapElement) +// { +// var builder = new TouchActions(this); +// IAction builtAction = builder.SingleTap(tapElement).Build(); +// builtAction.Perform(); +// } +// +// public void triggerDoubleTapEvent(WebElement tapElement) +// { +// var builder = new TouchAction(this); +// IAction builtAction = builder.DoubleTap(tapElement).Build(); +// builtAction.Perform(); +// } +// +// private void TriggerScrollEvent(WebElement tapElement, int xOffset, int yOffset) +// { +// var builder = new TouchActions(this); +// IAction builtAction = builder.Scroll(tapElement, xOffset, yOffset).Build(); +// builtAction.Perform(); +// } +// +// private void TriggerScrollEvent(int xOffset, int yOffset) +// { +// var builder = new TouchActions(this); +// IAction builtAction = builder.Scroll(xOffset, yOffset).Build(); +// builtAction.Perform(); +// } +// +// public void scrollPage(WebElement fromElement, WebElement toElement) +// { +// TriggerScrollEvent(fromElement, 0, toElement.Location.Y - fromElement.Location.Y); +// } +} diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/WebScreenshotPlugin.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/WebScreenshotPlugin.java index b9d2439e..b58a9ab0 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/WebScreenshotPlugin.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/WebScreenshotPlugin.java @@ -13,16 +13,17 @@ package solutions.bellatrix.web.infrastructure; -import lombok.SneakyThrows; import plugins.screenshots.ScreenshotPlugin; import ru.yandex.qatools.ashot.AShot; import ru.yandex.qatools.ashot.shooting.ShootingStrategies; import solutions.bellatrix.core.configuration.ConfigurationService; +import solutions.bellatrix.core.utilities.Log; import solutions.bellatrix.core.utilities.PathNormalizer; import solutions.bellatrix.web.configuration.WebSettings; import javax.imageio.ImageIO; import java.io.File; +import java.io.IOException; import java.nio.file.Paths; import java.util.UUID; @@ -32,13 +33,17 @@ public WebScreenshotPlugin() { } @Override - @SneakyThrows protected void takeScreenshot(String screenshotSaveDir, String filename) { var screenshot = new AShot() .shootingStrategy(ShootingStrategies.viewportPasting(100)) .takeScreenshot(DriverService.getWrappedDriver()); var destFile = new File(Paths.get(screenshotSaveDir, filename).toString()); - ImageIO.write(screenshot.getImage(), "png", destFile); + Log.info("Saving screenshot with path: " + destFile); + try { + ImageIO.write(screenshot.getImage(), "png", destFile); + } catch (IOException e) { + Log.error(e.toString()); + } } @Override diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/junit/WebTest.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/junit/WebTest.java index e6eb4f1e..a8849929 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/junit/WebTest.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/infrastructure/junit/WebTest.java @@ -17,6 +17,7 @@ import solutions.bellatrix.core.plugins.junit.BaseTest; import solutions.bellatrix.core.plugins.junit.TestResultWatcher; import solutions.bellatrix.web.components.listeners.BddConsoleLogging; +import solutions.bellatrix.web.components.listeners.BddToastNotificationsLogging; import solutions.bellatrix.web.components.listeners.HighlightElements; import solutions.bellatrix.web.infrastructure.BrowserLifecyclePlugin; import solutions.bellatrix.web.infrastructure.LogLifecyclePlugin; @@ -39,5 +40,6 @@ protected void configure() { addPlugin(LogLifecyclePlugin.class); addListener(BddConsoleLogging.class); addListener(HighlightElements.class); + addListener(BddToastNotificationsLogging.class); } } \ No newline at end of file diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/pages/PageMap.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/pages/PageMap.java index 360163a9..8f65abfb 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/pages/PageMap.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/pages/PageMap.java @@ -13,10 +13,15 @@ package solutions.bellatrix.web.pages; +import solutions.bellatrix.web.services.App; import solutions.bellatrix.web.services.ComponentCreateService; public abstract class PageMap { public ComponentCreateService create() { - return new ComponentCreateService(); + return app().create(); + } + + public App app() { + return new App(); } } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/pages/WebPage.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/pages/WebPage.java index 58604e17..e8bd15f1 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/pages/WebPage.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/pages/WebPage.java @@ -17,18 +17,24 @@ import solutions.bellatrix.web.services.App; import solutions.bellatrix.web.services.BrowserService; import solutions.bellatrix.web.services.ComponentCreateService; +import solutions.bellatrix.web.services.JavaScriptService; import solutions.bellatrix.web.services.NavigationService; import java.lang.reflect.ParameterizedType; public abstract class WebPage> { - public ComponentCreateService create() { - return new ComponentCreateService(); - } public BrowserService browser() { return new BrowserService(); } + public JavaScriptService javaScript() { + return new JavaScriptService(); + } + + public ComponentCreateService create() { + return new ComponentCreateService(); + } + public App app() { return new App(); } 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 1910596f..c4d9c675 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 @@ -13,17 +13,17 @@ package solutions.bellatrix.web.services; -import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringEscapeUtils; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.NotFoundException; -import org.openqa.selenium.StaleElementReferenceException; -import org.openqa.selenium.TimeoutException; +import org.junit.jupiter.api.Assertions; +import org.openqa.selenium.*; +import org.openqa.selenium.logging.LogEntry; +import org.openqa.selenium.logging.LogType; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.testng.Assert; import solutions.bellatrix.core.configuration.ConfigurationService; +import solutions.bellatrix.core.utilities.Log; import solutions.bellatrix.core.utilities.Wait; import solutions.bellatrix.web.components.Frame; import solutions.bellatrix.web.configuration.WebSettings; @@ -33,7 +33,11 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import java.util.function.Function; +import java.util.logging.Level; public class BrowserService extends WebService { private final JavascriptExecutor javascriptExecutor; @@ -92,10 +96,14 @@ public void switchToLastTab() { getWrappedDriver().switchTo().window(handles.stream().reduce((first, second) -> second).orElse("")); } - public void switchToTab(Runnable condition) throws InterruptedException { + public void switchToNewTab() { + getWrappedDriver().switchTo().newWindow(WindowType.TAB); + } + + public void switchToTab(Runnable condition) { Wait.retry(() -> { var handles = getWrappedDriver().getWindowHandles(); - Boolean shouldThrowException = true; + boolean shouldThrowException = true; for (var currentHandle : handles) { getWrappedDriver().switchTo().window(currentHandle); try { @@ -132,6 +140,14 @@ public void removeItemFromLocalStorage(String item) { ((JavascriptExecutor)getWrappedDriver()).executeScript(String.format("window.localStorage.removeItem('%s');", item)); } + public void scrollToBottom() { + ((JavascriptExecutor)getWrappedDriver()).executeScript("window.scrollTo(0, document.body.scrollHeight)"); + } + + public void scrollToTop() { + ((JavascriptExecutor)getWrappedDriver()).executeScript("window.scrollTo(0, 0)"); + } + public boolean isItemPresentInLocalStorage(String item) { return !(((JavascriptExecutor)getWrappedDriver()).executeScript(String.format("return window.localStorage.getItem('%s');", item)) == null); } @@ -149,6 +165,47 @@ public void clearLocalStorage() { ((JavascriptExecutor)getWrappedDriver()).executeScript("localStorage.clear()"); } + public List getBrowserLogs() { + return getLogsByType(LogType.BROWSER); + } + + public List getLogsByType(String type) { + try { + return getWrappedDriver().manage().logs().get(type).toJson(); + } + + catch (UnsupportedCommandException ex) { + // Unsupported browser + return new ArrayList<>(); + } + } + + public void assertNoConsoleErrorsLogged() { + Assertions.assertEquals(new ArrayList(), + getSevereLogEntries(), + "Severe Errors found in console. If they are expected, add them to the whitelist."); + } + + public void assertConsoleErrorLogged(String errorMessage, Level severity) { + var errorLogs = getLogsByType(LogType.BROWSER); + var filteredLog = errorLogs.stream().filter((log) -> log.getMessage().contains(errorMessage)).findFirst(); + Assertions.assertTrue(filteredLog.isPresent(), "Expected message '%s' not found in console. Actual Log: %s".formatted(errorMessage, errorLogs)); + Assertions.assertEquals(severity, + filteredLog.get().getLevel(), + "Log severity is not as expected for message '%s'.".formatted(errorMessage)); + } + + public List getSevereLogEntries() { + ArrayList whiteList = ConfigurationService.get(WebSettings.class).getConsoleErrorsWhitelist(); + + var logs = getBrowserLogs().stream().filter( + (logEntry -> + (logEntry.getLevel() == Level.SEVERE) && + !(whiteList.stream().anyMatch(listEntry -> logEntry.getMessage().contains(listEntry))) + )).toList(); + return logs; + } + public void waitForAjax() { long ajaxTimeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitForAjaxTimeout(); long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval(); @@ -211,62 +268,57 @@ public void waitUntilPageLoadsCompletely() { long waitUntilReadyTimeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitUntilReadyTimeout(); long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval(); var webDriverWait = new WebDriverWait(getWrappedDriver(), Duration.ofSeconds(waitUntilReadyTimeout), Duration.ofSeconds(sleepInterval)); - webDriverWait.until(webDriver -> ((JavascriptExecutor)webDriver).executeScript("return document.readyState").equals("complete")); + try { + webDriverWait.until(webDriver -> ((JavascriptExecutor)webDriver).executeScript("return document.readyState").equals("complete")); + } catch (ScriptTimeoutException ex) { + Log.error("Script timeout while loading for page load."); + } } - @SneakyThrows public void injectInfoNotificationToast(String message) { var timeout = ConfigurationService.get(WebSettings.class).getNotificationToastTimeout(); injectNotificationToast(message, timeout, ToastNotificationType.Information); } - @SneakyThrows public void injectInfoNotificationToast(String format, Object... args) { String message = String.format(format, args); var timeout = ConfigurationService.get(WebSettings.class).getNotificationToastTimeout(); injectNotificationToast(message, timeout, ToastNotificationType.Information); } - @SneakyThrows public void injectSuccessNotificationToast(String message) { var timeout = ConfigurationService.get(WebSettings.class).getNotificationToastTimeout(); injectNotificationToast(message, timeout, ToastNotificationType.Success); } - @SneakyThrows public void injectSuccessNotificationToast(String format, Object... args) { String message = String.format(format, args); var timeout = ConfigurationService.get(WebSettings.class).getNotificationToastTimeout(); injectNotificationToast(message, timeout, ToastNotificationType.Success); } - @SneakyThrows public void injectErrorNotificationToast(String message) { var timeout = ConfigurationService.get(WebSettings.class).getNotificationToastTimeout(); injectNotificationToast(message, timeout, ToastNotificationType.Error); } - @SneakyThrows public void injectErrorNotificationToast(String format, Object... args) { String message = String.format(format, args); var timeout = ConfigurationService.get(WebSettings.class).getNotificationToastTimeout(); injectNotificationToast(message, timeout, ToastNotificationType.Error); } - @SneakyThrows public void injectWarningNotificationToast(String message) { var timeout = ConfigurationService.get(WebSettings.class).getNotificationToastTimeout(); injectNotificationToast(message, timeout, ToastNotificationType.Warning); } - @SneakyThrows public void injectWarningNotificationToast(String format, Object... args) { String message = String.format(format, args); var timeout = ConfigurationService.get(WebSettings.class).getNotificationToastTimeout(); injectNotificationToast(message, timeout, ToastNotificationType.Warning); } - @SneakyThrows public void injectNotificationToast(String message, long timeoutMillis, ToastNotificationType type) { String escapedMessage = StringEscapeUtils.escapeEcmaScript(message); String executionScript = """ @@ -294,7 +346,11 @@ public void injectNotificationToast(String message, long timeoutMillis, ToastNot } window.$bellatrixToastContainer.appendChild($bellatrixToast); setTimeout($bellatrixToast.remove.bind($bellatrixToast), $timeout);"""; - ((JavascriptExecutor)getWrappedDriver()).executeScript(executionScript); + try { + ((JavascriptExecutor)getWrappedDriver()).executeScript(executionScript); + } catch (Exception ex) { + Log.error("Failed to inject notification toast."); + } } public void waitForReactPageLoadsCompletely() { @@ -305,6 +361,29 @@ public void waitForReactPageLoadsCompletely() { webDriverWait.until(d -> javascriptExecutor.executeScript("return window.performance.timing.loadEventEnd > 0")); } + // TODO Refactor the other methods to reuse this one + public void waitUntil(Function function) { + long waitUntilReadyTimeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitUntilReadyTimeout(); + long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval(); + var webDriverWait = new WebDriverWait(getWrappedDriver(), Duration.ofSeconds(waitUntilReadyTimeout), Duration.ofSeconds(sleepInterval)); + String message = Thread.currentThread().getStackTrace()[2].getMethodName(); + webDriverWait.withMessage("Timed out while executing method: %s".formatted(message)); + webDriverWait.until(function); + } + + public void tryWaitUntil(Function function) { + long waitUntilReadyTimeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitUntilReadyTimeout(); + long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval(); + var webDriverWait = new WebDriverWait(getWrappedDriver(), Duration.ofSeconds(waitUntilReadyTimeout), Duration.ofSeconds(sleepInterval)); + try { + String message = Thread.currentThread().getStackTrace()[2].getMethodName(); + webDriverWait.withMessage("Timed out while executing method: %s".formatted(message)); + webDriverWait.until(function); + } catch (TimeoutException exception) { + Log.error(String.format("Timed out waiting for the condition! %s", function.toString())); + } + } + public void waitForJavaScriptAnimations() { long waitForJavaScriptAnimationsTimeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitForJavaScriptAnimationsTimeout(); long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval(); @@ -326,6 +405,28 @@ public void waitNumberOfWindowsToBe(int numberOfWindows) { webDriverWait.until(ExpectedConditions.numberOfWindowsToBe(numberOfWindows)); } + public void waitForRequest(String partialUrl) { + var javascriptExecutor = (JavascriptExecutor)getWrappedDriver(); + String script = String.format("return performance.getEntriesByType('resource').filter(item => item.name.toLowerCase().includes('%s'))[0] !== undefined;", partialUrl); + + try { + waitUntil(e -> (boolean)javascriptExecutor.executeScript(script)); + } catch (TimeoutException exception) { + throw new TimeoutException(String.format("The expected request with URL '%s' is not loaded!", partialUrl)); + } + } + + public void tryWaitForRequest(String partialUrl) { + var javascriptExecutor = (JavascriptExecutor)getWrappedDriver(); + String script = "return performance.getEntriesByType('resource').filter(item => item.name.toLowerCase().includes('" + partialUrl.toLowerCase() + "'))[0] !== undefined;"; + + try { + waitUntil(e -> (boolean)javascriptExecutor.executeScript(script)); + } catch (Exception exception) { + Log.error("The expected request with URL '%s' is not loaded!", partialUrl); + } + } + public void waitForAngular() { long angularTimeout = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getWaitForAngularTimeout(); long sleepInterval = ConfigurationService.get(WebSettings.class).getTimeoutSettings().getSleepInterval(); @@ -416,4 +517,14 @@ public void assertUrlPathAndQuery(String pathAndQuery) { Assert.assertEquals(pathAndQuery, actualUri.getPath() + "?" + actualUri.getQuery(), "Expected URL is different than the Actual one."); } + + public String getLastClipboardEntry() { + JavaScriptService jsService = new JavaScriptService(); + Object lastCopyObject = jsService.execute("return await window.navigator.clipboard.readText();"); + if (lastCopyObject != null) { + return lastCopyObject.toString(); + } else { + return ""; + } + } } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/services/ToastNotificationType.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/services/ToastNotificationType.java index b1b4cbc6..a966768a 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/services/ToastNotificationType.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/services/ToastNotificationType.java @@ -5,4 +5,4 @@ public enum ToastNotificationType { Success, Warning, Error, -} +} \ No newline at end of file diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/validations/ComponentValidator.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/validations/ComponentValidator.java index 62cb06bf..fdc54287 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/validations/ComponentValidator.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/validations/ComponentValidator.java @@ -118,13 +118,20 @@ private static void waitUntil(BooleanSupplier condition, WebComponent com return condition.getAsBoolean(); }); } catch (TimeoutException ex) { - var error = String.format("\u001B[0mThe %s of \u001B[1m%s \u001B[2m(%s)\u001B[0m%n" + - " Should %s: \"\u001B[1m%s\u001B[0m\"%n" + - " %" + prefix.length() + "sBut was: \"\u001B[1m%s\u001B[0m\"%n" + - "Test failed on URL: \u001B[1m%s\u001B[0m", + var error = String.format("The %s of %s (%s)%n" + + " Should %s: \"%s\"%n" + + " %" + prefix.length() + "sBut was: \"%s\"%n" + + "Test failed on URL: %s", attributeName, component.getComponentClass().getSimpleName(), component.getFindStrategy(), prefix, value, "", supplier.get().toString().replaceAll("%n", "%n" + String.format("%" + (prefix.length() + 12) + "s", " ")), browserService.getUrl()); +// var colorFormattedError = String.format("\u001B[0mThe %s of \u001B[1m%s \u001B[2m(%s)\u001B[0m%n" + +// " Should %s: \"\u001B[1m%s\u001B[0m\"%n" + +// " %" + prefix.length() + "sBut was: \"\u001B[1m%s\u001B[0m\"%n" + +// "Test failed on URL: \u001B[1m%s\u001B[0m", +// attributeName, component.getComponentClass().getSimpleName(), component.getFindStrategy(), +// prefix, value, "", supplier.get().toString().replaceAll("%n", "%n" + String.format("%" + (prefix.length() + 12) + "s", " ")), +// browserService.getUrl()); Log.error("%n%n%s%n%n", error); throw new AssertionError(error, ex); } diff --git a/bellatrix.web/src/main/java/solutions/bellatrix/web/waitstrategies/WaitStrategy.java b/bellatrix.web/src/main/java/solutions/bellatrix/web/waitstrategies/WaitStrategy.java index f475c379..562942f3 100644 --- a/bellatrix.web/src/main/java/solutions/bellatrix/web/waitstrategies/WaitStrategy.java +++ b/bellatrix.web/src/main/java/solutions/bellatrix/web/waitstrategies/WaitStrategy.java @@ -40,6 +40,7 @@ public WaitStrategy(long timeoutInterval, long sleepInterval) { protected void waitUntil(Function waitCondition) { webDriverWait = new WebDriverWait(DriverService.getWrappedDriver(), Duration.ofSeconds(timeoutInterval), Duration.ofSeconds(sleepInterval)); + webDriverWait.withMessage(Thread.currentThread().getStackTrace()[2].getMethodName()); webDriverWait.until(waitCondition); }