Last time we were able to make a script that searches for and opens up a webpage. Handy, but each “action” that we do takes a couple of functions. Can we change this into organized behaviors and make it more reusable?
All of our code so far has been organized into a main class. This lends itself well to simple, straightforward programs. Currently, all we’ll ever do is open up Wikipedia and search for Selenium. Let’s aspire to be something more than that.
Setting Up a Test Class
Let’s say that we wanted to have a second, totally different search going on inside the script. In order to make this work, we would need to copy and paste our code inside the main block again and change a few search terms and validations. Then, whenever we wanted to run the second part of the script, we would need to wait for the first part to finish before it ever got to the second part. Call me impatient, but I think we can organize this into something better.
In the software testing world, short scripts like these are organized into tests that run their small routine and report back information to the user, like a pass or fail. Lets borrow that architecture to organize our code. You’ll notice in the project explorer that there is already a test directory created:

You can right click that folder and click New File to create a test class. I’ll call mine WikipediaTests.java
. Once we open it, we’ll see that its empty, besides a stub for the class. So what do we put inside?
In order to turn this into a test file, we’ll need to import a testing library. There are plenty of options for Java and Maven, but I’d recommend using JUnit. It’s one of the most popular frameworks, simple, and help is easy to find online. Installing it is made really easy by Maven, we follow the same process that we did for installing Selenium. Search the Maven repository for JUnit, copy the block of code with the <dependency> tag, and paste it into our pom.xml file. The next time we call mvn compile
it will download the files that we need.

Next we need to set up the JUnit structure. You can read the full getting started guide if you’re interested, but I’ll point out the important parts here. Import the libraries into the top of the WikipediaTests.java file:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
The first library includes the Test framework, and the second includes functions for asserting
values, like if something is true or false. To create our first test, inside the WikipediaTests
class make a function and add @Test
to the line above it. This tells the computer that the following function should be treated as a JUnit test.
@Test
public void Test() {...}
Lets put something simple inside of that. What could be more straightforward than 2+2=4? Create an integer variable that stores the result of our addition, then we can use that assertions library to test if the computer can do math correctly.
int result = 2+2;
assertEquals(4, result);
Once you have the test ready, you can type mvn compile in the terminal to make sure that this code is valid and the libraries get downloaded. With everything in place, you can click the Testing icon in the VS Code side bar (a lab beaker (an Erlenmeyer flask)) to open up the test explorer, and click a test to run. You can also click that little check mark inside the code window to rerun a test, and right click it for more advanced debugging options.

A test case! Lets port over our old code from the other function into a test function here. You’ll need to change the name of the function from main into something else, remove the function inputs, and add that test tag to it.

Now that the code has been moved to this class, we can delete the main class. We’ll be running things from this test explorer from now on. Thank the main class for it’s contributions to your life, journal, and whenever you’re ready, let’s move on to what we can do with these tests.
Creating Functions
Lets start at the top – without looking at the code, what are we doing? How might we want to organize those bits for reuse? If you’re stuck, try describing the actions of the test:
“Open the browser, navigate to Wikipedia, search for selenium, and check if we made it to the page.” – You, probably
Notice the sentence structure and the action verbs that we use. Open, navigate, search, check – each of these is standing in for a function. We could navigate to any webpage, and that makes this a reusable component. The adpositions, then, tell us what the variables are. I search for selenium. I validate the page url. Lets take what we have and abstract it into something we can reuse with variables.
To make a new place to put these functions, I made a file (right click the folder name -> new file) inside the src/main/java/com/example
directory called SeleniumFunctions.java
. Inside of it, I’m going to copy and paste the first two functions.

What’s up with the new class and function?
There are a few things happening automatically here. When you make that new file it’ll probably setup the class for you. For public class SeleniumFunctions
we’re making a public (aka unrestricted elsewhere in the code) class full of functions that we can import into that test file. For the function that we make, we want it to be public and there is no return necessary, so it has a void
type.
If we then try to call this OpenBrowser()
function inside the test… we get the dreaded red squiggly. Worse yet, the suggested fixes don’t seem to make any sense.

The option to create a method creates a new method inside of this test class, but we wanted to make it in it’s own file. That might seem unnecessary to you now, but it’ll set us up for success in the future. The other option is to configure a static import, and that’s a whole other thing that is also not worth getting into right now. Let’s try something else.
As a member of the class (outside of the function), we can declare an instance
of the SeleniumFunctions class. The syntax is like this:
ClassName variableName = new ClassName();
Once we do that, we’ll still see a squiggle. Thankfully, this is an easy reminder to import that libarary.

Then, with the context finally in place, we can call our local object, functions
, and append that to the OpenBrowser() command, so the compiler knows what we’re talking about.

But there’s a new issue with the code – since we moved that WebDriver driver
object into that separate function, it’s no longer in scope for the other functions to know about. So how do we resolve that?
Class and Instance Variables
This is starting to touch on the concepts of object-oriented-programming, something that’ll take much longer than an article to explain. Focus on the question for right now: “How can I access the driver in other functions?” We need to place a reference to that driver
object somewhere that can be called by multiple functions.
One way would be to move back that driver declaration into the test function, and let the OpenBrowser function return
the driver that it creates. It’s a different paradigm than we’re used to.

Once we return that value, we can have the instance
of that object in our test class be set by it.

We solved our problem! This is a perfectly serviceable solution using an instance
of an object that is used in the lifecycle of our test.
Let’s try walking this design down the road a little bit. Try to predict what that search function will need to look like. It needs to know the term we are searching for and it’ll need to know how to do it with the driver. Something like:
searchWikipedia(String term, WebDriver driver)
And that validate function for the page url, it’ll also need to know the expected title and the driver too.
validateURL(String url, WebDriver driver)
Ultimately, we’ll need to pass in the driver into every single function that uses this driver class. Which, given that this is a web automation project, will be most of them. Can we find a solution that keeps this from being so repetitive?
Lets rewind a little bit. Undo the changes to the OpenBrowser function and make it a void function again with no return. Put on a good breakup song. Where else can we declare this variable?
Re-visit the design behind our SeleniumFunctions class. We have an instance of that class inside of our test class, so that each test function can reference it. This solved our repetition problem. In the same way, can we have each function inside the SeleniumFunctions class reference a shared driver object?
Inside the SeleniumFunctions class, before the OpenBrowser() function, we can declare a WebDriver object that is a member of this class. Then, instead of returning the driver variable in the OpenBrowser function, we can reference the driver that is a part of the class and instantiate it here.

This way, all new functions we make inside the SeleniumFunctions class will be able to use a properly setup driver. The drawback to this design is that we can no longer call the driver object from the test class. Anytime we want to do something with the driver, we’ll need a function in the SeleniumFunctions class that does it. If you look over our code and try to slice it up into functions, you’ll notice that our future search and validate url functions exclude the line that navigates to Wikipedia:

Where does that fit in? We’ve got two options. We could:
- Create an independent navigate function
- Move it into our OpenBrowser function
There are valid reasons to do either solution, and we really don’t know what we need until we look a little further down the road. Will we need to navigate to other URLs other than when we open the browser? Do we always want to navigate to a URL when we open a browser? To know these answers, we have to consider the scope of our project and how it might change and grow. This is why so many projects struggle to grow – often time limitations are built-in early on that make it harder to change. Sometimes projects are so over-engineered from the start that they’re strangled. Ultimtaely, what is it that you want?
Yeah, sure, okay, but what is the actual correct answer?
Create flexibility in your implementations. There’s little harm to implementing a navigate function that we can delete later. You can make a different type signature for the OpenBrowser function that includes a URL as an input, and if so, navigates to it. It took me longer to type all of this than implementing that. Don’t overthink things too much, and don’t be down on yourself if you get it wrong either. You’re predicting the future over here.
To keep it simple, the canonical answer will be moving that navigate to the OpenBrowser function. We probably will be going to a URL every time we open a browser. It’d be pretty boring if we didn’t. Here’s my new function, where I include a URL as an input to the function and driver.get
navigates to it:
public void OpenBrowser(String URL) {
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get(URL);
}
Then, inside the test, we can navigate to Wikipedia:
functions.OpenBrowser("https://en.wikipedia.org/wiki/Main_Page");
Pretty cool, huh? We’re deciding who has what responsibility and how we want our code to scale. Lets see how the rest of it pans out.
Fully Abstracting the Script
Lets finish up with these other functions. The search
function probably doesn’t need to output anything, and the only input we need is the search term. Try starting by copying over that whole block into a new function inside SeleniumFunctions
:

Like before, we can move the term
variable into that sendKeys
function to make this a fully abstracted function that’ll search for anything on Wikipedia:

The ValidateURL function will likewise need no return value and one string input. If we copy and paste our block from the main script here and change the validation code to use the title variable we pass in, we have a complete function.

With everything in place, we can call the new functions in our test and try it out:

Look at how simple it is now! At a glace, we can clearly see every step that the test is taking. Organizing groups of code into functions is what turns a “script” into a “program”. Your ability to recognize patterns and discern how they’ll work in the long term is what turns a “coder” into an “engineer”. Not that you need to be one – the key is to do what interests you and grow your skillset.
Since we did such a good job of engineering our program, lets try making another test case to see how it scales. Lets search for something interesting. You know how chairs sometimes have cloths on the top of them? The term for that is an antimacassar. What an interesting word.
With a new SearchAntimacassar
test function, we can copy right over the OpenBrowser
function since we’re going to Wikipedia again. Our Search term becomes “antimacassar”, and the title validation becomes the Wikipedia page.

Wasn’t that simple? Just like that, we have our second test! You can copy and paste this for any number of searches that you might want, and this setup will scale effectively.
The quality of the building blocks you setup in your project will determine how high you can stack it. What other functions can you create to model the behaviors you’ll want in web automation?
Leave a Reply