Table of Contents
Framework Structure
It is in this part of the series that you will start actually writing code. If you are somewhat new to coding in Java and Selenium, I recommend writing out all the code snippets from scratch rather than copying and pasting them, for some of the following reasons;
- As you start writing code, you will start picking up on patterns in the code, which will help you pick up things quicker
- As you write out the code and try to reference certain libraries, IntelliJ will auto-suggest references to import for you, which will also help you learn the code that you are writing 🙂
utils.drivers package
Editing the ‘ChromeWebDriver’ Class
- Open up the ‘ChromeWebDriver’ class in the ‘utils.drivers’ package and edit the class file to match below, to add functionality to setup and load the ChromeWebDriver specifically. Also note here how we use WebDriverManager to download and setup the ChromeDriver before we execute any other methods in the ChromeWebDriver class…
package utils.drivers; import io.github.bonigarcia.wdm.WebDriverManager; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeDriverService; import org.openqa.selenium.chrome.ChromeOptions; public class ChromeWebDriver { private static WebDriver driver; public static void setupChromeDriver() { WebDriverManager.chromedriver().setup(); } public static WebDriver loadChromeDriver(String chromeArgument) { setupChromeDriver(); ChromeDriverService driverService = ChromeDriverService.createDefaultService(); ChromeOptions options = new ChromeOptions(); options.addArguments(chromeArgument); driver = new ChromeDriver(driverService, options); return driver; } }
Editing the ‘FirefoxWebDriver’ Class
- Open up the ‘FirefoxWebDriver’ class in the ‘utils.driver’ package and edit the class file to match below, to add functionality to setup and load the FirefoxWebDriver specifically. Also note here how we use WebDriverManager to download and setup the ChromeDriver before we execute any other methods in the FirefoxWebDriver class…
package utils.drivers; import io.github.bonigarcia.wdm.WebDriverManager; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxOptions; public class FirefoxWebDriver { private static WebDriver driver; private static void setupFirefoxDriver() { WebDriverManager.firefoxdriver().setup(); } public static WebDriver loadFirefoxDriver(String firefoxArgument) { setupFirefoxDriver(); FirefoxOptions options = new FirefoxOptions(); options.addArguments(firefoxArgument); driver = new FirefoxDriver(options); return driver; } }
utils.selenium package
Editing the ‘DriverController’ Class
- Open up the ‘DriverController’ class in the ‘utils.selenium’ package and edit the class file to match below, to create an instance of DriverController which can be used to start either the Chrome or Firefox driver (we have also used Log4J here to report any errors when stopping the WebDriver)…
package utils.selenium; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.openqa.selenium.WebDriver; import utils.drivers.ChromeWebDriver; import utils.drivers.FirefoxWebDriver; public class DriverController { public static DriverController instance = new DriverController(); WebDriver webDriver; private static Logger log = LogManager.getLogger(DriverController.class.getName()); public void startChrome(String arg) { if(instance.webDriver != null) return; instance.webDriver = ChromeWebDriver.loadChromeDriver(arg); } public void startFirefox(String arg) { if(instance.webDriver != null) return; instance.webDriver = FirefoxWebDriver.loadFirefoxDriver(arg); } public void stopWebDriver() { if (instance.webDriver == null) return; try { instance.webDriver.quit(); instance.webDriver.close(); } catch (Exception e) { log.error(e + "::WebDriver stop error"); } instance.webDriver = null; log.debug(":: WebDriver stopped"); } }
Editing the ‘Driver’ Class
- Open up the ‘Driver’ class in the ‘utils.selenium’ package and edit the class file to match below, to create a public WebDriver instance which returns DriverController when initiated, which can then be used in test methods (e.g. maximising the browser() window)…
package utils.selenium; import org.openqa.selenium.WebDriver; public class Driver { public static WebDriver browser() { return DriverController.instance.webDriver; } }
Editing the ‘Settings’ Class
- Open up the ‘Settings’ class in the utils.selenium’ package and edit the class file to match below. We will be using the Settings file to store public static variables that we will want to be using in our project. For the purposes of this course, I will be explaining how to automate a simple DuckDuckGo search, therefore I will set my baseUrl to start.duckduckgo.com…
package utils.selenium; public class Settings { public static String baseUrl = "https://start.duckduckgo.com/"; }
utils.hooks package
For now, we will only be adding Before and After hooks for the top-level Cucumber scenarios/feature files.
Editing the ‘CucumberHooks’ Class
- Open up the ‘CucumberHooks’ class in the ‘utils.hooks’ package and edit the class file to match below. This class file will contain all our Before and After hooks for our Cucumber scenarios, which respectively get executed before or after a Cucumber feature and/or scenario. Please see https://github.com/cucumber/cucumber/wiki/Hooks for more information…
package utils.hooks; import cucumber.api.java.After; import cucumber.api.java.Before; import utils.selenium.DriverController; public class CucumberHooks { @Before("@Chrome") public void beforeChrome(){ DriverController.instance.startChrome("--disable-extensions"); } @Before("@Firefox") public void beforeFirefox() throws Exception { DriverController.instance.startFirefox("--disable-extensions"); } @Before("@HeadlessChrome") public void beforeChromeHeadless() { DriverController.instance.startChrome("--headless"); } @Before("@HeadlessFirefox") public void beforeHeadlessFirefox() throws Exception { DriverController.instance.startFirefox("--headless"); } @After public void stopWebDriver() { DriverController.instance.stopWebDriver(); } }
As you can see above, I have used Cucumber tags in my hooks (see https://github.com/cucumber/cucumber/wiki/Tags for more information) to launch different browsers with different arguments passed (e.g. headless browser or normal browser with extensions disabled), depending on the Cucumber tag the Feature or Scenario being tested has.
pages package
Editing the ‘BasePage’ Class
- Open up the ‘BasePage’ class in the ‘pages’ package and edit the class file to match below. The ‘BasePage’ class is where we will later put all our test methods for our BaseScenarios, and any methods that exist / can be shared across more than one specific page object (e.g. main navigation, validate a passed pageUrl etc)…
package pages; import static utils.selenium.Driver.browser; import org.openqa.selenium.WebDriver; public class BasePage extends Page { public WebDriver driver = browser(); }
Editing the ‘Page’ Class
- Open up the ‘Page’ class in the ‘pages’ package and edit the class file to match below. The ‘Page’ class is where we create a generic method called instanceOf that takes the same generic class and initialises a new object from PageFactory with the correct elements for the page. All page classes inherit in some way from ‘BasePage’, which in turn inherits from this ‘Page’ class
package pages; import org.openqa.selenium.support.PageFactory; import static utils.selenium.Driver.browser; public class Page { public static <T extends BasePage> T instanceOf(Class<T> clazz) { return PageFactory.initElements(browser(), clazz); } }
Step Definitions
Editing the ‘BaseSteps’ Class
- Open up the ‘BaseSteps’ class in the ‘steps’ package and edit it to match as below so that it inherits from the Page class.
package steps; import pages.Page; public class BaseSteps extends Page { // this file is a placeholder to follow POM concepts. }
Editing all the other Step Definitions
- Do the same thing to all other Step Definition class files, making them also inherit from the Page class
TestNG
With TestNG, a TestRunner class can be made which defines everything that you want to happen that group of tests. To work with Cucumber, you must define the directory where your Feature files are located in the project, as well as define where the glue is that connects your Feature files to your java test methods (both hooks and step definitions). The TestRunner class can also define what happens before, after and during the tests are run.
Creating the ‘TestRunner’ Class
- Right-click the src/test/java directory, select ‘New’ –> ‘Java Class’ and name the class “TestRunner”
- Add the following lines of code in to the ‘TestRunner’ class (also note the @CucumberOptions, where the location of the feature files, glue files and any formatter plugins are defined)…
import io.cucumber.testng.AbstractTestNGCucumberTests; import io.cucumber.testng.CucumberOptions; @CucumberOptions( features = "src/test/resources/features", glue = {"utils.hooks", "steps"}, tags = {"~@Ignore"}, plugin = {"html:target/cucumber-reports/cucumber-pretty", "json:target/cucumber-reports/CucumberTestReport.json", "rerun:target/cucumber-reports/rerun.txt" }) public class TestRunner extends AbstractTestNGCucumberTests { }
Interestingly, you can actually make as many TestRunner classes as you see fit for your project and then define which TestRunner classes, if any, get run within a test suite. You define all of this within a file called ‘testng.xml’.
Creating the ‘testng.xml’ File
- Right-click the project root in the Project Explorer, select ‘New’ –> ‘File’ and create a file called ‘testng.xml’ and press the ‘OK’ button
- Open up the ‘testng.xml’ file and add the following lines of XML into the file (using similar/appropriate names for your own test suite and test name)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Product Test Suite" verbose="1" parallel="tests" thread-count="1" configfailurepolicy="continue"> <test name="Product Acceptance Tests" annotations="JDK" preserve-order="true"> <classes> <class name="TestRunner"/> </classes> </test> </suite>
Log4J2
Apache Log4j is a Java-based logging utility. It was originally written by Ceki Gülcü and is part of the Apache Logging Services project of the Apache Software Foundation. Log4j is one of several Java logging frameworks.
Configuring Log4J2
In our framework, we will create a Log4J2 config file and use it to configure our logging to the console. You can also use Appender class in Log4J2 with a config file to configure logging to files, however we will stick to the Console in this blog series.
You can have one of many different types of Log4J2 config files, with certain filetypes being read before falling back to others.
In our framework, we will go with a simple XML config file.
For more info, please visit https://logging.apache.org/log4j/2.x/manual/configuration.html
Log4J2 XML File
- Right-click on the ‘/src/test/resources‘ directory and select ‘New’ -> ‘File’, name the file ‘log4j2-test.xml’ and press OK
- Paste the following into the xml file, it should look like below…
<?xml version="1.0" encoding="UTF-8"?> <Configuration> <Appenders> <Console name="Console"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %c{1} -%msg%n"/> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>
Building the Project
Building the Maven project
- Open CMD-prompt / Terminal and cd (change directory) into the root of your Maven project. For example…
$ cd /Users/username/DevProjects/ProductAutomationFramework
(without typing the $)
- Enter the following command to build your Maven project…
$ mvn clean install -DskipTests
(without typing the $. We also currently have no tests. If we don’t put in the skipTests command, we will get a build error around one of the cucumber report plugins we are using).
Building the Gradle project
If you have instead setup the project with Gradle instead of Maven, you can follow the steps below to build…
- Open CMD-prompt / Terminal and cd (change directory) into the root of your Maven project. For example…
$ cd /Users/username/DevProjects/ProductAutomationFramework
(without typing the $)
- Enter the following command to build your Maven project…
$ ./gradlew build
(without typing the $)
The core framework is now setup! In the next part, we will see how to create our Base Scenarios.
Copyright secured by Digiprove © 2018