There's one thing on this form that's different and that's this dropdown menu.
Dropdown menus are not represented by the WebElement class. They have their own special class. That's what we'll look at in this section.
So, revisiting the-internet application, let's talk about a new scenario that we're going to automate now.
From the Home page, we’re going to click on the Dropdown link
Then we are going to select an option from the dropdown
Then we're going to make sure that it's selected
Which means that we're going to have to interact with two pages. We've already have a class for the Home page, so we don't have to create that.
We only need to create a new class for this Dropdown page.
Let’s do that now.
We'll go to our framework under “pages” package and let's create a new class that's called DropdownPage
.
We want to create our WebDriver instance and our constructor.
public class DropdownPage {
private WebDriver driver;
private By dropdown = By.id("dropdown");
public DropdownPage(WebDriver driver){
this.driver = driver;
}
}
Now, even though we do have a HomePage
class, it does not have a method to click on the dropdown link.
So, we need to add that to that class. Let's go and do that now.
We'll go inside of HomePage
because that's where the link is, and we can create another link here.
Now here's the thing, when we create another link here, we'll need to create another method and that's fine.
However, I think there were 45 links when we ran that test to see how many links there were. And that could get really long. This is where you start designing your class to be a little bit flexible.
Instead of creating links for each one, they're all going to follow the same pattern. They're going to have some text, and this is going to be the variable.
Let's go ahead and just make a generic method here called clickLink
.
This one is not going to return a new page object.. I'll tell you why in a moment.
This one will take a String and we'll just call this linkText
.
We're going to say driver.findElement
and we know that we're going to use linkText
for all of these links on this page.
So, we can just say By.linkText
and pass in the linkText
that they give us and then do a .click
.
public void clickLink(String linkText){
driver.findElement(By.linkText(linkText)).click();
}
Then in this clickFormAuthenticationLink
method, instead of us doing the findElement.click
, we're just going to make a call to this clickLink
method and we're going to pass in our text.
public LoginPage clickFormAuthenticationLink(){
clickLink("Form Authentication");
return new LoginPage(driver);
}
After we do the clickLink
this is going to return the LoginPage
.
So that's why we didn't need to return anything in the clickLink
because this is going to be a generic method that we're going to use for all of our links. We don't know what it's going to return so we don't have to worry about that.
But each of these methods that we create, these link ones, we can just call into that.
If we want to, we could make this clickLink
method private so that our tests don't call this because we want our test to be able to get a page object back.
We can remove this field — private By formAuthenticationLink = By.linkText("Form Authentication");
— we no longer need this.
Now I'm putting in the locator inside of this method because this is going to be the only method that is utilizing this.
NOTE
If I ever added another method that also uses “Form Authentication”, then I would move this outside of this method and make it global so that I'm not repeating the locators across multiple methods. Because if I do and this locator changes, I would have to change it in multiple places and that's not good.
So, for the most part you'll put your locators at the global scope, but in this case, I'm going to put it in the method and that's okay. As long as it's only in one place, then you're okay.
All right, so back to adding our method to click on the dropdown page.
public DropdownPage clickDropDown(){
clickLink("Dropdown");
return new DropdownPage(driver);
}
Okay, so that takes care of our first action in our tests.
We're going to have to click the Dropdown link and after that, we’ll be taken to the Dropdown page.
So, we go to the DropdownPage
class and we'll need to add some more fields and methods for the rest of the actions in our test.
Let's look at the app again.
We want to get a handle to this dropdown element and it has an id called dropdown
. We can just copy this and we'll add this to our framework.
private By dropdown = By.id("dropdown");
Now we want to create a method that is going to select something from the dropdown.
In our framework, we want to be able to select anything, so we wouldn't create a method to select just the first option or the second option. We want to be able to select any of the options from the dropdown.
So, we'll take a variable.
public void selectFromDropDown(String option){
}
And inside of here, instead of doing driver.findElement
which would return a web element — again, the dropdown is not a web element — there's actually a class just for dropdown elements called Select
.
However, it is not in the dependencies that we already have so far.
We need to go back to our pom.xml
file and we're going to add another Selenium dependency here.
If we go back to our Maven repository and let's look for “selenium support” and we see that there is this library.
Selenium Support contains a class called Select
and this is what we need if we want to interaction with dropdowns.
We copy this and let's added to our pom
file.
We go back to our pom
file and I'm going to add this right after the other selenium one.
So, we have selenium-chrome-driver, now we're adding selenium-support.
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-support</artifactId>
<version>3.141.59</version>
</dependency>
And again, this one contains the Select
class.
So back to our DropdownPage
, now I can create the Select
object and we see here that it's under the org.openqa.selenium.support.ui
package..
So, we choose that one and notice that it imported this here.
public void selectFromDropDown(String option){
Select dropdownElement = new Select(driver.findElement(dropdown));
}
This takes a WebElement
so we can do the driver.findElement
because we know that that'll give us a WebElement
and we pass in this dropdown
locator. That will give us an element and we pass that element to Select
Now that we have dropdownElement
, we can see that there are new methods here that were not available on the WebElement
class itself.
These are for a dropdown menu.
You can getAllSelectedOptions
You can selectByVisibleText
— that's the text that we actually see in the dropdown options
You can deselectAll
You can deselectByIndex
— so I can say deselect the first or the second ones and this index is zero based, meaning that the first element is 0
You can deselectByValue
Let me show you what “value” is.
Under this <select>
, we have these options and we see that one value equals 1 and the other value equals 2 but the visible text is “Option 1” and “Option 2”. So, you can select by values, text, or index.
You can also getFirstSelectedOption
— which will give you a WebElement
You can get all of the options with getOptions
— which would be a list of WebElement
s
You can do a getWrappedElement
You can ask isMultiple
You can selectByIndex
and selectByValue
So, for this specific method we've asked them to pass in a String for the visible texts. so, let's do selectByVisibleText
and we'll pass in this option
that was given to us.
public void selectFromDropDown(String option){
Select dropdownElement = new Select(driver.findElement(dropdown));
dropdownElement().selectByVisibleText(option);
}
So that will select the option from the dropdown.
Now we'll need a method that gets the selected option so that we can assert on this for our test.
Let's create that. And this one needs to return a String because it'll return that text value.
public String getSelectedOption(){
}
Now I'm going to need to create this dropdownElement
again, which I don't want to do.
I'm going to make a little private method here so that I'm not repeating the same thing for every method because any method in here that I create later on is going to have to work with this dropdown.
So, this one will be private — it's not for my test to access, it's just a little helper method for me.
This will return this Select
and we'll call this findDropDownElement
and return the new Select
instance.
So, I paste that there:
public void selectFromDropDown(String option){
findDropDownElement().selectByVisibleText(option);
}
private Select findDropDownElement(){
return new Select(driver.findElement(dropdown));
}
Okay, and then here I can call this again and this time I want to say get.
.
Now I can say get all the selected options, get the first selected option or get options (selected or deselected)
I want to get all of the selected options so that the test can make sure that the only thing selected is what it's expecting to be selected.
So instead of returning just one String, we'll have to change this to a list of Strings:
public List<String> getSelectedOption(){
findDropDownElement().getAllSelectedOptions();
}
So, I have all of these, and this is a list of web elements, but what I want is the Strings, the text, because I don't want to return a list of web elements back to my test.
The reason for that is this is a framework class. Inside of my test class, I shouldn't mess with things like web elements or any of these classes from Selenium. So, all of that Selenium stuff should be contained within the framework piece.
I need to do a little bit more coding here to extract all of the text from the option fields and store that as a list.
Let's just start by storing this in a list of web elements [List<WebElement>
] so that we know what we have and we don't get lost.
public List<String> getSelectedOption(){
List<WebElement> selectedElements = findDropDownElement().getAllSelectedOptions();
}
Next I'm going to do stream
and I'm going to say I want to map
.
public List<String> getSelectedOptions(){
List<WebElement> selectedElements = findDropDownElement().getAllSelectedOptions();
return selectedElements.stream().map()
}
And inside of this map I can pass in a Lambda expression. So, I want to say for every element that's inside of this list, I want you to take this action.
So, you give it an arrow [ ->
] and the action I want you to take is to get the text.
public List<String> getSelectedOptions(){
List<WebElement> selectedElements = findDropDownElement().getAllSelectedOptions();
return selectedElements.stream().map(e->e.getText())
}
And once you get the text from every element, I want you to collect all of those into a new List and that will be a List of Strings.
public List<String> getSelectedOption(){
List<WebElement> selectedElements = findDropDownElement().getAllSelectedOptions();
return selectedElements.stream().map(e->e.getText()).collect(Collectors.toList());
}
Alternative Approach
You could have done this the long way as well. You could have looped through each of these and then added them to a new List yourself. But this is a newer approach in Java to be able to collect these into a list.
And since we've changed this to be a List and not just one String, I'm going to make this method name plural [changing from getSelectedOption
to getSelectedOptions
].
package pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Select;
import java.util.List;
import java.util.stream.Collectors;
public class DropdownPage {
private WebDriver driver;
private By dropdown = By.id("dropdown");
public DropdownPage(WebDriver driver){
this.driver = driver;
}
public void selectFromDropDown(String option){
findDropDownElement().selectByVisibleText(option);
}
public List<String> getSelectedOptions(){
List<WebElement> selectedElements = findDropDownElement().getAllSelectedOptions();
return selectedElements.stream().map(e->e.getText()).collect(Collectors.toList());
}
private Select findDropDownElement(){
return new Select(driver.findElement(dropdown));
}
}
Now let's go ahead and create our test, since we have all of our framework methods available.
This is not a Login test, it's not a Base test, it is a new test for Dropdown.
So, let's make a new package here and we'll call this one dropdown
.
We'll add a new class and I'll call this one DropdownTest
and this one is going to inherit from BaseTests
because all of our tests are inheriting from BaseTests
.
We'll use the @Test
annotation and we'll make a new test called testSelectedOption
.
We know we have access to the homePage
object — which represents where we end up when this text first launches.
And we want to call the clickDropDown
method — that will take us to the Dropdown page. We can store that in a variable.
I'm just going to use the var
so I don't have to type out the long class name and we'll call this dropDownPage
.
public class DropdownTests extends BaseTests {
@Test
public void testSelectOption(){
var dropDownPage = homePage.clickDropDown();
}
}
So that saves us a little bit on verboseness.
Now let’s select from the dropdown.
@Test
public void testSelectOption(){
var dropDownPage = homePage.clickDropDown();
dropDownPage.selectFromDropDown();
}
And we need to give it an option. Let's go to our application.
The first option text is called “Option 1”. Let's just go ahead and copy this and place it in our test, so I say select that option.
@Test
public void testSelectOption(){
var dropDownPage = homePage.clickDropDown();
dropDownPage.selectFromDropDown("Option 1");
}
Now I want to make sure that that's the option that's selected.
I can do dropDownPage
again and say get the selected options [ getSelectedOptions
], which will return this as a List.
@Test
public void testSelectOption(){
var dropDownPage = homePage.clickDropDown();
dropDownPage.selectFromDropDown("Option 1");
var selectedOptions = dropDownPage.getSelectedOptions();
}
Now I can do assertions.
So, when I think about this:
I want to assert that this list only contains one item, meaning there's only one thing that's selected.
Then I also want to assert that the thing that is selected has the text of “Option 1”.
So, I'm going to write two assertions in this test.
Let's go ahead and say assertEquals
because I want to make sure that the number of selected options equals to 1.
We'll say that the actual value is selectedOptions.size()
— this will give us the number of selected items and we expect this to be 1.
And then the error message here will say "Incorrect number of selections".
assertEquals(selectedOptions.size(), 1, "Incorrect number of selections");
Okay, so now I've made sure that this is 1. If it's not 1, our test is going to fail right there. It won't even go to the next assertion.
But if it does work, if there's only 1, we want another assertion here to make sure that one equals our option text [“Option 1”].
We're going to have to use this option text String again.
Because I'm using this again, I'm going to store it in a variable.
So, let's say:
String option = "Option 1";
dropDownPage.selectFromDropDown(option);
And we will then pass it into this selectFromDropDown
and we can use it again in another assert.
This time I'm going to use assertTrue
.
For assertTrue
, I want to make sure that this list contains "Option 1".
So, I can say selectedOptions.contains
the option that I actually selected. And if this fails, I'll write the message "Option not selected".
assertTrue(selectedOptions.contains(option), "Option not selected");
package dropdown;
import base.BaseTests;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
public class DropdownTests extends BaseTests {
@Test
public void testSelectOption(){
var dropDownPage = homePage.clickDropDown();
String option = "Option 1";
dropDownPage.selectFromDropDown(option);
var selectedOptions = dropDownPage.getSelectedOptions();
assertEquals(selectedOptions.size(), 1, "Incorrect number of selections");
assertTrue(selectedOptions.contains(option), "Option not selected");
}
}
Okay, so let's run this.
We right click and do a run and we see it going; and it went really fast.
But we saw that it went to the Dropdown page and the test passed. So, we know that there was only one thing selected and that it contained “Option 1”.
So that is the Select
class.
Again, the Select
class is not a web element.
It's its own object and it's in a different library than the WebElement
class. We utilize Select
to interact with dropdown menus.
For your optional independent exercise, go to our site, The Internet, and click on the Forgot Password link.
Enter an email address here. The email can be anything.
For example, “tau@example.com” so it doesn't have to be a real email address.
Click this Retrieve Password button and verify on this new page that you have this message saying, “Your e-mail's been sent!”.
Good luck!
Solution
Programming can be done many different ways, but here’s my solution: ForgotPasswordPage, EmailSentPage, ForgotPasswordTests.