DEV Community

Miklós Szeles
Miklós Szeles

Posted on • Originally published at mszeles.com on

Selenide, I think this is the beginning of a beautiful friendship

Image description
As I promised we will start coding in this section as soon as possible. In case you missed my previous article, I highly recommend reading it beforehand, as I have provided many reasons why you should give Selenide a try.

Setup

  • You will need the Java Development Kit. I suggest getting the latest Open JDK 17.
  • As an IDE I am using IntelliJ IDEA, but you can use anything you like. Besides IntelliJ, Eclipse and Visual Studio Code are the most popular ones.
  • For source code management I use Git , you can find the project on GitHub.
  • I usually use Maven for Java projects, but this is the perfect opportunity to learn something new, so I will use Gradle this time. In case I make any stupid mistake with Gradle, do not hesitate to tell me 😉
  • As Java testing framework TestNG and JUnit are the most popular ones. I will go with TestNG but I will provide the required info for JUnit 5 as well. That's all you will need for the project.

Let's continue with creating a Gradle project and openening the build.gradle file. Let's add the dependencies:

dependencies {
    testImplementation 'org.testng:testng:7.5'
    implementation 'com.codeborne:selenide:6.2.1'
    implementation 'org.seleniumhq.selenium:selenium-java:4.1.1'
}

Enter fullscreen mode Exit fullscreen mode

For JUnit:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
    implementation 'com.codeborne:selenide:6.2.1'
    implementation 'org.seleniumhq.selenium:selenium-java:4.1.1'
}

Enter fullscreen mode Exit fullscreen mode

Specify to use TestNG:

test {
    useTestNG()
}

Enter fullscreen mode Exit fullscreen mode

for JUnit:

test {
    useJUnitPlatform()
}

Enter fullscreen mode Exit fullscreen mode

We are ready to go. Only one question remains. Which site will we test?

Have you ever enrolled in a highly rated course to learn about test automation just to realize half of the pages which were used as examples in the course are not existing anymore or have been changed completely? Well, it happened to me. My choice is (almost) all developers' second favourite site (right after Google) Stackoverflow. My third favourite is Baeldung if you are interested 😊. I am pretty sure Stackoverflow won't disappear in the near future, but of course, I cannot guarantee it won't change its design.

It is time to write our first test. Yayyy. Let's start with something simple.

Open Stackoverflow

Using Selenide you do not have to use the WebDriver directly as it is automatically opening and closing the browser for us using Chrome by default. We can open the page with one single line without any extra configuration.

@Test
public void openPage() {
    open("https://stackoverflow.com/");
}

Enter fullscreen mode Exit fullscreen mode

Just run the test and you will see Stackoverflow opening and then closing immediately. Whenever I develop and look for DOM elements, I prefer to open the same page in my browser in incognito mode so I can see exactly the same result I would see whenever I open it via Selenide. Let's do this now.

Stackowerflow homepage

The first thing I notice is that we have an accept cookie banner on this page. As in these days, we have something similar on every website, let's accept the cookie. But before that let's finish our first test. Every worthwhile test should check some conditions.

Checking visibility and enabled state of elements

Of course, we could select many conditions to test, but in this case, I will concentrate on demonstrating the different ways of Selenide condition checking. Let's verify we have a visible Login and a Sign up button. In order to do that, first, we have to find the element. Looking at the DOM in Chrome dev tools (or of course, you can use Firefox or other tools if you prefer that) we can see that the Login button is actually a link. It has no id, but it has a class login-link which seems unique. So I could use a simple CSS selector or even an id based selector. But is it really unique?

If you watch carefully, you can notice despite its name, the Sign up link has the same login-link class as the Login button, so I will have to select based on an index. For that purpose, I am using XPath (in general prefer CSS over XPath as it is faster ).

Stackoverflow Sign Up link source code

You might think we should use the text to find the element as it is unique, but I do not recommend that as according to my experience texts are changing more often than ids and classes , not even talking about testing with different languages.

Let's write the selector:

$x("(//a[contains(@class, 'login-link')])[1]");

Enter fullscreen mode Exit fullscreen mode

It is very simple, concise and straightforward. You can use $(String cssSelector), $$(String cssSelector) to find an element($) or elements($$) by CSS. Similarly, you can use $x(String xpath) and $$x(String xpath) to find elements using XPath. Of course, you can use the built-in Selenium By commands also, using $(By) and $$(By). Selenide even provides new selectors in addition to the normal Selenium selectors like byText, withTextand byAttribute.

In case you are not familiar with the basic Selenium selectors, I recommend again Rahul Shetty's Udemy Course on Selenium. You will have a good grasp of them after finishing the course. One additional resource which is quite good for beginners is CSS Dinner.

Now that we have the element which is a SelenideElement (subclass of WebElement) we can verify its visibility:

$x("(//a[contains(@class, 'login-link')])[1]").shouldBe(visible); It is easy to read this code. Actually writing it is also easy and fun. 😀 You could ask what happens in case the element is not immediately present in the DOM , or what if it is present but it needs some time to become visible? Selenide uses implicit waits both during finding elements and also during evaluating conditions. The default is 4 sec , but it can be configured. The should functions ( should, shouldHave, shouldBe, ShouldNot, shouldNotHave, shouldNotBe ) even have an overloaded version which also accepts a Duration to specify the timeout explicitly.

You can run your test from your favourite IDE, or you can run it from the command line using Gradle:

gradlew test

Enter fullscreen mode Exit fullscreen mode

Simply change the "visible" to "enabled" to check the enabled state of an element:

$x("(//a[contains(@class, 'login-link')])[1]").shouldBe(enabled);

Enter fullscreen mode Exit fullscreen mode

Based on the previous example it is quite easy to write the verification for the Sign Up link:

$x("(//a[contains(@class, 'login-link')])[2]").shouldBe(visible);
$x("(//a[contains(@class, 'login-link')])[2]").shouldBe(enabled);

Enter fullscreen mode Exit fullscreen mode

Let's refactor

Before moving on, let's do some refactoring , so we can make the code cleaner and we can get one step closer to the Page Object Model about which I will write in a later article. Let's do 2 things. First, extract the Login and Sign Up elements as constants using meaningful names. In case you are worried about locating an element before the page even exists, then I have some good news for you. Selenium (so also Selenide) use lazy initialization which means the elements will only be located, whenever you call some methods on them. Second, we will use method chaining to get a more concise code. I really like this technique as you will see it in later articles. So here is the code after refactor:

public class HomepageTest {

    private final SelenideElement loginLink = $x("(//a[contains(@class, 'login-link')])[1]");
    private final SelenideElement signUpLink = $x("(//a[contains(@class, 'login-link')])[2]");

    @Test
    public void openPage() {
        open("https://stackoverflow.com/");
        loginLink.shouldBe(visible).shouldBe(enabled);
        signUpButton.shouldBe(visible).shouldBe(enabled);
    }
}

Enter fullscreen mode Exit fullscreen mode

Finding elements based on text

As I promised, we will get to the cookie banner soon. Just one more thing before that. As I said it is not the best idea to locate elements based on texts, but sometimes you simply have no other options (of course you can find everything with absolute xpath expressions, but that is a horrible idea as it will break on the slightest change of the UI), or you have to check if elements have a certain text. This time we will stick to English but later I will write an article about internationalization.

Let's find the link with the text "About" and verify whether it is pointing to the right location.

We can choose from a couple of options here:

Finding using XPath

$x("//a[text()='About']");
$x("//a[contains(text(), 'bout')]");

Enter fullscreen mode Exit fullscreen mode

You can check for exact matches or partial matches with XPath.

Finding using linkText or partialLinkText

$(By.linkText("About"));
$(By.partialLinkText("bout"));

Enter fullscreen mode Exit fullscreen mode

You can find the element using the standard Selenium locators.

Find using byText and withText $(byText("About")) $(withText("bout"));; You can also find an element using Selenide locators. byText is similar to linkText as it finds an element using an exact match for the text, and withText is similar to partialLinkText as it checks whether the element's text contains the given text or not. But there is a huge advantage here for Selenide: it works with every kind of element , not just with links.

Verifying link reference

This is quite simple:

aboutLink.shouldHave(href("/company"));

Enter fullscreen mode Exit fullscreen mode

I highly recommend checking the Conditions class's documentation, so you can get a good grasp on the different conditions offered by Selenide.

Closing the banner

Finally, we arrived at the last topic of today's article which is accepting the cookies. Let's create another test called acceptCookies. First, let's find the accept button:

Stackoweflow source code part for accept cookie button

Looking at the page source we can see it is a button that we have to click and it has a class js-accept-cookies which can identify the button. So we can easily find it by class name:

$(byClassName("js-accept-cookies"));

Enter fullscreen mode Exit fullscreen mode

After finding the button we can click it by simply using the click() method on it as we could have already learnt it using Selenium.

acceptCookiesButton.click();

Enter fullscreen mode Exit fullscreen mode

In case the test does not fail we can tell we were able to click the accept cookie button, but to be on the safe side let's verify whether the banner has really disappeared. The banner is a div having a class js-consent-banner. In order to verify it is not there anymore we can use the shouldNotBe method:

private final SelenideElement acceptCookieBanner = $("div.js-consent-banner");
.
.
acceptCookieBanner.shouldNotBe(visible);

Enter fullscreen mode Exit fullscreen mode

or you can write:

acceptCookieBanner.shouldBe(hidden);

Enter fullscreen mode Exit fullscreen mode

Actually should, shouldBe and shouldHave are synonyms similarly to their counterparts shouldNot, shouldNotBe, shouldNotHave. They are interchangeable and also there are many synonyms among the conditions, so basically it is up to you which you find more readable. These examples mean the same:

acceptCookieBanner.shouldBe(hidden);
acceptCookieBanner.shouldNotBe(visible);
acceptCookieBanner.should(disappear);
acceptCookieBanner.shouldNot(appear);

Enter fullscreen mode Exit fullscreen mode

I prefer not using negation so I am going with shouldBe(hidden).

Finishing touches

Before we finish let's do 2 more refactors to reduce duplication and to make the code even simpler.

As we are testing the same homepage in these tests, we can move the page opening to a common part that executes before each test. In TestNG you can use the @BeforeMethod annotation for this, using JUnit 5 the @BeforeEach will be your solution. To get a good comparison of the usage of the two frameworks, check this article on Baeldung.

When verifying the Login and Sign Up link we used 2 chained shouldBe methods to verify whether the component is visible and enabled. Actually, you can merge them into one call , as the should methods can accept multiple conditions.

This is the final code:

public class HomepageTest {
    private final SelenideElement loginLink = $x("(//a[contains(@class, 'login-link')])[1]");
    private final SelenideElement signUpLink = $x("(//a[contains(@class, 'login-link')])[2]");
    private final SelenideElement aboutLink = $(byText("About"));
    private final SelenideElement acceptCookiesButton = $(byClassName("js-accept-cookies"));
    private final SelenideElement acceptCookieBanner = $("div.js-consent-banner");

    @BeforeMethod
    public void setup() {
        open("https://stackoverflow.com/");
    }

    @Test
    public void openPage() {
        loginLink.shouldBe(visible, enabled);
        signUpLink.shouldBe(visible, enabled);
        aboutLink.shouldHave(href("/company"));
    }

    @Test
    public void acceptCookies() {
        acceptCookiesButton.click();
        acceptCookieBanner.shouldBe(hidden);
    }
}

Enter fullscreen mode Exit fullscreen mode

It looks neat, don't you think? 😊

And that's all. We have finished today's lesson. I hope you enjoyed it. Don't hesitate to share your thoughts about this article and suggest topics for upcoming ones. In the next post, I will develop tests for Stackoverflow's Questions page.

📚 Before we finish, I give you an assignment. I used XPath to find the Sign up link. Write a CSS selector (not using the text of the link) to find the Sign up link! Please do not post your solution immediately, give some times to the others. So I ask you to post your solution on Monday ❣️

Buy me a coffee!

See you soon...

Github: https://github.com/mszeles/selenide-tutorial

📚Join the Selenide community on LinkedIn! ✌

P.S.: Support me by sharing this article if you like it. 👍

Top comments (1)

Collapse
 
mszeles profile image
Miklós Szeles

📚Join the Selenide community on LinkedIn . ✌