Angular UI Testing using Selenium and Selenide

In-browser UI testing of Angular applications poses several challenges such as asynchronous operations and unpredictable loading screens with spinners or dimmers.
Selenide has an implicit capability to wait for the loading screen to disappear.

Angular UI Testing using Selenium and Selenide

In-browser UI testing of Angular applications poses several challenges such as asynchronous operations and unpredictable loading screens with spinners or dimmers. Although Selenium is an excellent tool for UI testing, it does not provide built-in mechanisms to wait for the disappearance of loading indicators automatically. This is where Selenide helps immensely. Let us learn more.

Angular UI Testing using Selenium and Selenide

1. Handling Loading Screens in Selenium vs. Selenide

When using pure Selenium, handling loading screens efficiently can be challenging. Selenium does not provide built-in mechanisms to automatically wait for the disappearance of loading indicators. Testers typically need to write explicit waits. For example,

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.invisibilityOfElementLocated(By.id("dimmer-element-id")));

This requires manually defining loading conditions, which can be error-prone and increase test complexity.

Selenide, on the other hand, has an implicit capability to wait for the loading screen to disappear. This ensures that the test script does not proceed until the loading screen is completely gone. This makes tests more stable and reduces unnecessary explicit waits.

@Slf4j
public class ElementUtils {

	//...

	public static SelenideElement findElement(By by, int timeout) {

	  log.info("Finding element: {}", by);
	  try {
	    return $(by).shouldBe(Condition.visible, Duration.ofSeconds(timeout));
	  } catch (AssertionError e) {
	    log.warn("Element not found within timeout: {}", e.getMessage());
	    return null;
	  }
	}
}

2. Maven

Selenide works on top of Selenium so we need to include both dependencies along with other required dependencies.

<properties>
  <webdrivermanager.version>5.9.2</webdrivermanager.version>
  <selenium.version>4.8.1</selenium.chrome.version>
  <selenide.version>7.7.0</selenide.version>
</properties>

<dependencies>
  <dependency>
    <groupId>io.github.bonigarcia</groupId>
    <artifactId>webdrivermanager</artifactId>
    <version>${webdrivermanager.version}</version>
  </dependency>
  <dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>${selenium.version}</version>
  </dependency>
  <dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-chrome-driver</artifactId>
    <version>${selenium.version}</version>
  </dependency>
  <dependency>
    <groupId>com.codeborne</groupId>
    <artifactId>selenide</artifactId>
    <version>${selenide.version}</version>
  </dependency>

  <!-- Include JUnit 5 dependencies-->
</dependencies>

If using Gradle, include these in your build.gradle:

dependencies {
    testImplementation "io.github.bonigarcia:webdrivermanager:5.9.2"
    testImplementation "org.seleniumhq.selenium:selenium-java:4.8.1"
    testImplementation "org.seleniumhq.selenium:selenium-chrome-driver:4.8.1"
    testImplementation "com.codeborne:selenide:7.7.0"
}

3. Setting Up WebDriver

There will always be browser version mismatch issues if we rely on the browser installed on the test machine. For that, this is highly recommended to use the WebDriverManager library. It is an open-source Java library that carries out the management (i.e., download, setup, and maintenance) of the drivers required by Selenium WebDriver in a fully automated manner.

<dependency>
  <groupId>io.github.bonigarcia</groupId>
  <artifactId>webdrivermanager</artifactId>
  <version>${webdrivermanager.version}</version>
</dependency>

To automate the driver management, we need to select a given manager in the WebDriverMager API (e.g., chromedriver() for Chrome) and invoke the method setup().

@BeforeAll
static void setup() {
  WebDriverManager.chromedriver().setup();
}

When executed, the setup() method tries to find the browser version installed on the machine. Then, using the browser version, it tries to find the proper driver version through various methods. Once the driver version is discovered, WebDriverManager downloads the driver to a local cache (located at ~/.cache/selenium) and exports the driver path using Java system properties. These drivers are reused in subsequent calls.

We can further customize the behavior of the browser (auto-close of failures, extensions, logging levels etc) by configuring the browserCapabilities property.

import com.codeborne.selenide.Selenide;
import com.codeborne.selenide.Condition;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static com.codeborne.selenide.Selenide.*;
import static com.codeborne.selenide.Condition.*;

public class LoginTest {

	@BeforeAll
	static void setup() {

		WebDriverManager.chromedriver().setup();

		Configuration.baseUrl = BASE_URL;
		Configuration.browser = "chrome";
		Configuration.holdBrowserOpen = true;
		Configuration.browserCapabilities = getBrowserOptions();
	}

	private static ChromeOptions getBrowserOptions() {

		ChromeOptions options = new ChromeOptions();
		options.setHeadless(false);
		options.addArguments("start-maximized");
		options.addArguments("--disable-infobars");
		options.addArguments("--disable-extensions");
		options.addArguments("--disable-gpu");
		options.addArguments("--disable-dev-shm-usage");
		options.addArguments("--no-sandbox");
		options.addArguments("--disable-in-process-stack-traces");
		options.addArguments("--disable-logging");
		options.addArguments("--log-level=3");
		options.addArguments("--remote-allow-origins=*");
		return options;
	}

	@BeforeEach
	void openBrowser() {
		open("https://your-angular-app.com/login");
	}

	// tests...
}

4. Writing a Simple JUnit 5 Test for UI Testing

The following is an example of a basic test for verifying an Angular login page. It waits for the login screen to load fully by detecting the username and password fields. When fields are loaded, it inputs the login credentials into respective input fields and submits the form.

After the login form is submitted, it waits for the login success/failure message and then verifies the message.

public class LoginTest {

  // Setup driver manager

  @Test
  void loginWithValidCredentials() {
      $("#username").setValue("testuser");
      $("#password").setValue("password");
      $("#loginButton").click();
      $("#welcomeMessage").shouldHave(text("Welcome, testuser"));
  }

  @AfterEach
  void closeBrowser() {
      Selenide.closeWebDriver();
  }
}

5. Handling Element Lookup Failures

Selenide has a default timeout of 30 seconds and this is sufficiently large. It means that Selenide will wait for 30 seconds after the page loads for the element to appear. If the element does not appear (visible), Selenide will throw NoSuchElementException or AssertionError exceptions.

$("#nonexistent").click(); // Waits for 30 seconds, then throws AssertionError

We can use assertions to customize this behavior. For example, we can use methods like shouldBe or shouldHave to customize the timeout duration and then throw the exception. This causes the test method to fail.

//Waits for 10 seconds and then throws an exception
$("#nonexistent").shouldBe(visible, Duration.ofSeconds(10));  // throws TimeoutException after 10 seconds

If we do not want to throw an exception, but rather provide alternative handling for missing elements, we can use the exists() to isDisplayed() methods. Using these methods, selenide will not throw an exception and will return true or false.

boolean isVisible = $("#nonexistent").isDisplayed(); // Returns false

6. Important Methods for Finding and Verifying Elements

For a quick reference, we can use the following methods for locating and verifying the HTML elements in the webpage loaded into the browser.

MethodDescription
$(“#elementId”)Finds an element by ID or selector.
$(By.xpath(“//div[@class=’example’]”))Finds an element using XPath.
$(“.className”).shouldBe(visible)Verifies if an element is visible.
$(“#inputField”).setValue(“Test”)Sets a value in an input field.
$(“button”).click()Clicks a button.
$(“#message”).shouldHave(text(“Success”))Verifies if an element contains specific text.
$(“#checkbox”).shouldBe(checked)Verifies if a checkbox is selected.
$(“#dropdown”).selectOption(“Option 1”)Selects an option from a dropdown.
$$(“.list-items”).size()Gets the number of elements matching a selector.
$$(“.row”).get(2).click()Finds and clicks the third element in a list.
$(“#loading”).should(disappear)Waits until an element disappears.
$(“#error”).exists()Checks if an element exists without throwing an exception.

7. Generating Test Reports

7.1. Default Reporting

Selenide provides built-in support for test reports by generating screenshots and logs automatically. To enable reports, configure Selenide to take screenshots and save test logs:

Configuration.screenshots = true;
Configuration.savePageSource = true;

Once tests are executed, Selenide automatically stores the following artifacts in the build/reports/tests or target/reports/tests directory:

  • Screenshots: Captured on failure.
  • HTML source: Saved for failed tests.
  • Logs: Browser console logs.

7.2. Advanced Reporting with Allure

Allure is a powerful tool for test reporting using Selenide. Allure provides an interactive UI showing test execution history, logs, screenshots, and failure reasons.

To integrate Allure into the project tests, add its latest dependency:

<dependency>
    <groupId>io.qameta.allure</groupId>
    <artifactId>allure-selenide</artifactId>
    <version>2.29.1</version>
</dependency>

Then enable the Allure reporting by adding a listener:

@BeforeAll
static void setUp() {

    //Other setup code

    SelenideLogger.addListener("AllureSelenide", new AllureSelenide());
}

After running the tests, we can check out the generated report in build/allure-results directory. To see the report in the browser, run the command:

allure serve build/allure-results

8. Summary

This short but useful tutorial demonstrates how to effectively use Selenium and Selenide for UI testing of an Angular application. Without using Selenide, you may face a lot of issues due to loading screens and missing elements within the defined timeout periods.

When using Selenide, we can safely avoid lots of issues that commonly occur in Angular UI testing.

Happy Learning !!

Weekly Newsletter

Stay Up-to-Date with Our Weekly Updates. Right into Your Inbox.

Comments

Subscribe
Notify of
0 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.