Browse Category: Framework Designing

Selenium Testing beyond GUI Browsers.

“Selenium automates browsers”. So goes the introductory line on Seleniumhq.org. To what extent is this a limitation?

The topic of the blog provokes questions – Can we structure Selenium-based test framework to test beyond GUI browsers? If not, at the very least, how can we improve test-effectiveness by extending an existing Selenium test-suite to work with other test-actions that do not use browsers.

The first question above needs in-depth technical discussion. We focus on the second one here which can be addressed at a more conceptual level.

We have an example below which illustrates how one can improve the efficiency and coverage of automation by going beyond the Browser GUI and enabling backend operations.


Consider a simple web-application which has Selenium test suite containing hundreds of automated scripts that need to run daily. In addition, there are test-scenarios that need to be selectively executed during the day using some of the scripts. The framework is robust and the application relatively stable.

All scripts need to necessarily navigate across the web-application to get to specific web-pages. There are often many test scenarios that need to be executed on a specific web-page. The time for the script to get to that specific web-page depends on the performance of the application and latency in loading the intermediate web-pages. This would in turn increase the overall time needed to execute the automation suite when the script count is in hundreds. How can this be reduced?

Problem Statement: how do we limit the steps of navigating the web front-end using Selenium scripts so as to reduce the overall test-execution time?

Refer the illustration below.

Selenium Beyond GUI

Total Execution Time – 2 minutes.

We have the above scenario wherein there are parameters to be set on Web-Page-B which are necessary for validations on Web-Page-C. Every individual script logs-out and logs-on to the application, and total time taken is close to 2 minutes.

Now if there are 50 scripts that set different parameter combinations, the Selenium suite that runs scripts on the browser GUI would potentially take 50 * 2 = 100 minutes just for navigating back and forth on the web pages, especially if we need to log in and log out after every script.

The actual verification point however is only on Web-Page-C for every kind of parameter setting.

Selenium Beyond GUI img 2

Solution:

The parameter setting could be handled by Python or Perl scripts running in the backend. This would then cut down the navigation on the GUI.

Selenium Beyond GUI -img 3

The test flow is handled as below.

·       We test the end-to-end GUI navigation one time. The first test scenario covers this part.

·       At the same time, we trigger a script that directly accesses the backend. There could be several ways to do this – server side scripts, API calls, database queries… This depends on the application architecture and what is being tested.

·       The parameter is set at backend, the validation is done by the Selenium script on web-page C, this step is iterated over 50 parameters to be set for the 50 scenarios to be tested

The key here is to enable the automation framework to detect when the backend parameter is set to progress onto GUI validation, and then continue iterating between the two steps.

Total execution time now comes down drastically since webpage navigation is no longer needed.

This is an example of how existing Selenium suite can be extended with backend operations that improve automation efficiency and overall test effectiveness. The concept is proven; the implementation is heavily dependent on application architecture and specific test scenarios.

[bws_google_captcha]

Test Automation in Selenium Using Page Object Model and Page Factory

Writing automated tests is more than just a luxury for any agile software development team. It is a need, and is an essential tool to find bugs quickly during early phases of software development cycles. When there is a new feature that is still in development phase, developers can run automated tests and see how other parts of the system are affected by those changes.

Through test automation, it is possible to lower the cost of bug fixing and bring overall improvement to quality assurance (QA) process. With proper tests, developers get a chance at finding and resolving bugs even before it gets to QA. Test automation further helps us to automate test cases and features that are constantly regressing. This way QAs have more time in testing other parts of the application. Moreover, this helps in ensuring quality of the product in production releases. As a result, we get products that are effectively more stable, and a QA process that is more efficient.

clip_image001

Selenium simplifies test automation for web applications

Although writing automated tests may seem like an easy task for developers and engineers, there is still the possibility of ending up with poorly implemented tests, and the high cost of code maintenance in any agile process. Trying to constantly deliver changes or features in any agile development project can prove to be costly when tests are involved. Changing one element on a web page that 20 tests rely on will require one to go through these 20 test routines and update each one to adapt to this newly introduced change. Not only can this be really time consuming, but a serious de-motivating factor when it comes to implementing automated tests early on.

But, what if we could make the change in one place only, and have every relevant test routine use it? In this article, we will take a look at automated tests in Selenium, and how we can use Page Object models to write maintainable and reusable test routines.

Page Object Model in Selenium

Page Object model is an object design pattern in Selenium, where web pages are represented as classes, and the various elements on the page are defined as variables on the class. All possible user interactions can then be implemented as methods on the class:

clickLoginButton();
setCredentials(user_name,user_password);

Since well-named methods in classes are easy to read, this works as an elegant way to implement test routines that are both readable and easier to maintain or update in the future. For example:

In order to support Page Object model, we use Page Factory. Page Factory is an extension to Page Object and can be used in various ways. In this case we will use Page Factory to initialize web elements that are defined in web page classes or Page Objects.

Web page classes or Page Objects containing web elements need to be initialized using Page Factory before the web element variables can be used. This can be done simply through the use of initElements function on PageFactory:

LoginPage page = new LoginPage(driver);
PageFactory.initElements(driver, page);

Or, even simpler:

LoginPage page = PageFactory.intElements(driver,LoginPage.class)

Or, inside the web page class constructor:

public LoginPage(WebDriver driver) {           
         this.driver = driver; 
         PageFactory.initElements(driver, this);
}

Page Factory will initialize every WebElement variable with a reference to a corresponding element on the actual web page based on configured “locators”. This is done through the use of @FindBy annotations. With this annotation, we can define a strategy for looking up the element, along with the necessary information for identifying it:

@FindBy(how=How.NAME, using="username")
private WebElement user_name;

Every time a method is called on this WebElement variable, the driver will first find it on the current page and then simulate the interaction. In case we are working with a simple page, we know that we will find the element on the page every time we look for it, and we also know that we will eventually navigate away from this page and not return to it, we can cache the looked up field by using another simple annotation:

@FindBy(how=How.NAME, using="username")
@CacheLookup
private WebElement user_name;

This entire definition of the WebElement variable can be replaced with its much more concise form:

@FindBy(name="username")
private WebElement user_name;

The @FindBy annotation supports a handful of other strategies that make things a bit easier:

id, name, className, css, tagName, linkText, partialLinkText, xpath

@FindBy(id="username")
private WebElement user_name; 

@FindBy(name="passsword")
private WebElement user_password;

@FindBy(className="h3")
private WebElement label;

@FindBy(css=”#content”)
private WebElement text;

Once initialized, these WebElement variables can then be used to interact with the corresponding elements on the page. The following code will, for example:

user_password.sendKeys(password);

… send the given sequence of keystrokes to the password field on the page, and it is equivalent to:

driver.findElement(By.name(“user_password”)).sendKeys(password);

Moving on, you will often come across situations where you need to find a list of elements on a page, and that is when @FindBys comes in handy:

@FindBys(@FindBy(css=”div[class=’yt-lockup-tile yt-lockup-video’]”)))
private List<WebElement> videoElements;

The above code will find all the div elements having two class names “yt-lockup-tile” and “yt-lockup-video”. We can simplify this even more by replacing it with the following:

@FindBy(how=How.CSS,using="div[class=’yt-lockup-tile yt-lockup-video’]")
private List<WebElement> videoElements;

Additionally, you can use @FindAll with multiple @FindBy annotations to look for elements that match any of the given locators:

@FindAll({@FindBy(how=How.ID, using=”username”),

@FindBy(className=”username-field”)})
private WebElement user_name;

Now that we can represent web pages as Java classes and use Page Factory to initialize WebElement variables easily, it is time we see how we can write simple Selenium tests using PO and PF patterns.

Simple Test Automation Project in Java

For our simple project let’s automate developer sign up for Toptal. To do that, we need to automate the following steps:

· Visit www.toptal.com

· Click on the “Apply As A Developer” button

· On Portal Page first check if it’s opened

· Click on the “Join Toptal” button

· Fill out the form

· Submit the form by clicking on “Join Toptal” button

Setting Up a Project

· Download and install Java JDK

· Download and install InteliJ Idea

· Create a new Maven project

· Link “Project SDK” to your JDK, e.g.: on Windows “C:\Program Files\Java\jdkxxx”

· Setup groupId and artifactId:

<groupId>SeleniumTEST</groupId>
<artifactId>Test</artifactId>

· Add dependencies Selenium and JUnit Maven in your project POM file

   <dependencies>
        <!-- JUnit -->         
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
 
        <!-- Selenium -->
 
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-firefox-driver</artifactId>
            <version>${selenium.version}</version>
        </dependency>
 
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-support</artifactId>
            <version>${selenium.version}</version>
        </dependency>
 
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.version}</version>
        </dependency>
 
    </dependencies>

Replace Selenium version and JUnit Version with latest version numbers that can be found by searching for JUnit Maven on Google and on Selenium site.

At this point, if auto build is enabled, dependencies should start downloading automatically. If not, just activate Plugins > install > install:install under the Maven Projects panel on the right side of your IntelliJ Idea IDE.

clip_image002

Once the project has been bootstrapped, we can start creating our test package under “src/test/java”. Name the package “com.toptal”, and create two more packages under it: “com.toptal.webpages” and “com.toptal.tests”.

clip_image003

We will keep our Page Object/Page Factory classes under “com.toptal.webpages” and the test routines under “com.toptal.tests”.

Now, we can start creating our Page Object classes.

HomePage Page Object

The very first one we need to implement is for Toptal’s homepage (www.toptal.com). Create a class under “com.toptal.webpages” and name it “HomePage”.

package com.toptal.webpages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
import org.openqa.selenium.support.PageFactory;
 
 public class HomePage {
   private WebDriver driver;
 
   //Page URL
   private static String PAGE_URL="https://www.toptal.com";
 
   //Locators
 
   //Apply as Developer Button
   @FindBy(how = How.LINK_TEXT, using = "APPLY AS A DEVELOPER")
   private WebElement developerApplyButton;
 
   //Constructor
   public HomePage(WebDriver driver){
       this.driver=driver;
       driver.get(PAGE_URL);
       //Initialise Elements
       PageFactory.initElements(driver, this);
   }
 
   public void clickOnDeveloperApplyButton(){
 
       developerApplyButton.click();
 
   }
}
Determining Element Locators

On Toptal’s homepage we are interested about one element in particular, and that is the “Apply as a Developer” button. We can find this element by matching the text, which is what we are doing above. While modeling web pages as Page Object classes, finding and identifying elements can often become a chore. With Google Chrome or Firefox’s debugging tools, this can be made easier. By right clicking on any element on a page, you can activate the “Inspect Element” option from the context menu to find out detailed information about the element.

One common (and my preferred) way is to find elements using Firefox’s FireBug extension, in combination with Firefox web driver in Selenium. After installing and enabling FireBug extension, you can right click on the page and select “Inspect element with FireBug” to open FireBug. From the HTML tab of FireBug, you can copy the XPath, CSS Path, Tag name or “Id” (if available) of any element on the page.

clip_image004

By copying the XPath of the element in the screenshot above, we can create a WebElement field for it in our Page Object as follows:
@FindBy(xpath = “/html/body/div[1]/div/div/header/div/h1”)
WebElement heading;
Or to keep things simple, we can use the tag name “h1” here, as long as it uniquely identifies the element we are interested in:

@FindBy(tagName = "h1")
WebElement heading;
DeveloperPortalPage Page Object

Next, we need a Page Object that represents the developer portal page, one that we can reach by clicking on the “Apply As A Developer” button.

On this page, we have two elements of interest. To determine if the page has loaded, we want to verify the existence of the heading. And we also want a WebElement field for the “Join Toptal” button.

package com.toptal.webpages;
 
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
 
public class DeveloperPortalPage {
   private WebDriver driver;
 
   @FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1")
   private WebElement heading;
 
   @FindBy(linkText = "JOIN TOPTAL")
   private WebElement joinToptalButton;
 
   //Constructor
   public DeveloperPortalPage (WebDriver driver){
       this.driver=driver;
 
       //Initialise Elements
       PageFactory.initElements(driver, this);
   }
 
   //We will use this boolean for assertion. To check if page is opened
   public boolean isPageOpened(){
       return heading.getText().toString().contains("Developer portal");
   }
 
   public void clikOnJoin(){
       joinToptalButton.click();
   }
}
DeveloperApplyPage Page Object

And finally, for our third and last page object for this project, we define one that represents the page containing developer application form. Since we have to deal with a number of form fields here, we define one WebElement variable for every form field. We find each field by their “id” and we define special setter methods for every field that simulate keystrokes for the corresponding fields.
package com.toptal.webpages;
 

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
 
public class DeveloperApplyPage {
   private WebDriver driver;
 
   @FindBy(tagName = "h1")
   WebElement heading;
 
   @FindBy(id="developer_email")
   WebElement developer_email;
 
   @FindBy(id = "developer_password")
   WebElement developer_password;
 
   @FindBy(id = "developer_password_confirmation")
   WebElement developer_password_confirmation;
 
   @FindBy(id = "developer_full_name")
   WebElement developer_full_name;
 
   @FindBy(id = "developer_skype")
   WebElement developer_skype;
 
   @FindBy(id ="save_new_developer")
   WebElement join_toptal_button;
 
 
   //Constructor
   public DeveloperApplyPage(WebDriver driver){
       this.driver=driver;
 
       //Initialise Elements
       PageFactory.initElements(driver, this);
   }
 
   public void setDeveloper_email(String email){
       developer_email.clear();
       developer_email.sendKeys(email);
   }
 
   public void setDeveloper_password(String password){
       developer_password.clear();
       developer_password.sendKeys(password);
   }
 
public void  setDeveloper_password_confirmation(String password_confirmation){
       developer_password_confirmation.clear();
       developer_password_confirmation.sendKeys(password_confirmation);
   }
 
   public void setDeveloper_full_name (String fullname){
       developer_full_name.clear();
       developer_full_name.sendKeys(fullname);
   }
 
   public void setDeveloper_skype (String skype){
       developer_skype.clear();
       developer_skype.sendKeys(skype);
   }
 
   public void clickOnJoin(){
       join_toptal_button.click();
   }
   public boolean isPageOpened(){
       //Assertion
       return heading.getText().toString().contains("Apply to join our network as a developer");
   }
}
Writing a Simple Test

With Page Object classes representing our pages, and user interactions as their methods, we can now write our simple test routine as a series of simple method calls and assertions.

package com.toptal.tests;
 
import com.toptal.webpages.DeveloperApplyPage;
import com.toptal.webpages.DeveloperPortalPage;
import com.toptal.webpages.HomePage;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
 
import java.net.URL;
import java.util.concurrent.TimeUnit;
 
public class ApplyAsDeveloperTest {
   WebDriver driver;
 
   @Before
   public void setup(){
       //use FF Driver
       driver = new FirefoxDriver();
       driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
   }
 
   @Test
   public void applyAsDeveloper() {
       //Create object of HomePage Class
       HomePage home = new HomePage(driver);
       home.clickOnDeveloperApplyButton();
 
       //Create object of DeveloperPortalPage
       DeveloperPortalPage devportal= new DeveloperPortalPage(driver);
 
       //Check if page is opened
       Assert.assertTrue(devportal.isPageOpened());
 
       //Click on Join Toptal
       devportal.clikOnJoin();
 
       //Create object of DeveloperApplyPage
       DeveloperApplyPage applyPage =new DeveloperApplyPage(driver);
 
       //Check if page is opened
       Assert.assertTrue(applyPage.isPageOpened());
 
       //Fill up data
       applyPage.setDeveloper_email("[email protected]");
       applyPage.setDeveloper_full_name("Dejan Zivanovic Automated Test");
       applyPage.setDeveloper_password("password123");
       applyPage.setDeveloper_password_confirmation("password123");
       applyPage.setDeveloper_skype("automated_test_skype");
 
       //Click on join
       //applyPage.clickOnJoin(); 
   }
 
    @After
    public void close(){
          driver.close();
       }
   }
Running the Test

At this point, your project structure should look like this:

clip_image005

If you want to run the test, select “ApplyAsDeveloperTest” from the tree, right click on it and then select Run ‘ApplyAsDeveloperTest’.

clip_image006

Once the test has been run, you can see the results in the lower-left corner of your IDE:

clip_image007

Conclusion

Page Object and Page Factory make it easy to model web pages in Selenium and test them automatically and make the life of both developers and QAs much more simpler. When done right, these Page Object classes can be reused across your entire test suite and to give yourself the opportunity to implement automated Selenium tests for your projects early on, without compromising agile development. By abstracting away user interactions in your page object models and keeping your test routines light and simple, you can adapt your test suite to changing requirements with little effort.

I hope I have managed to show you how to write nice and clean test code that is easy to maintain. I will end the article with my favorite QA quote:

Think twice, code once!

This article originally appeared on Toptal.

A couple of words about Page Object

Often in the testing of web applications, every form on a page is described as a separate entity (object) – usually, one single form (in most cases it represents an isolated functionality) is equal to a separate class. All these objects/classes consist of web element instances (various web elements located on the related web form) and methods for interaction with the elements. Let’s imagine there is a Login form in a web application. The form may be described as:

public class Login
{
   IWebDriver driver;
   public Login(IWebDriver driver) { this.driver = driver; }
   //the class contains only one method
   //this is abridged example of the method. There is neither verification for null reference exception, nor check of driver’s actions, nor logging of events, nor handling of exceptions.
   //The method takes two strings as parameters: user name and password, and returns an instance of the web application start page - StartPage
   public StartPage LogIn(string userName, string pwd)
   {
      IWebElement name = driver.FindElement(By.nameTextboxLocator);
      name.SendKeys(userName);
      IWebElement pwd = driver.FindElement(By.passwordTextboxLocator);
      pwd.SendKeys(userPassword);
      IWebElement btnLogin = driver.FindElement(By.loginButtonLocator);
      btnLogin.Click();
      return new StartPage([parameters]);
   }
}

Therefore, the test would be looking like this:

[Test]
public class LogIn()
{
   driver.Navigate().GoTo(http://address-of-tested-login-page.com);
   var Page_Login = new Login(driver);
   var StartPage = Page_Login.LogIn(userName, password);
   Assert.IsTrue(StartPage.VerifyStartPageIsLoaded());
}

This approach allows you to create a big number of tests very fast and easy. But there are a number of underwater stones hidden inside as well. This way of writing new tests is good enough for little and simple projects where there are only a couple of hundreds of web elements and methods. and were only a few persons are doing the testing. Because, if the tested web application is very complex or the application is developed very fast and a lot of functionalities are often changed or you have a big team of automated testers (and most of them are juniors), it is likely that the maintenance of the framework will be your nightmare. Because sometimes you will be forced and called upon to maintain the framework.

A simple example: what if after clicking LogIn button a dozen of other different pages may be loaded in addition to StartPage? For example, imagine that according to the type of user’s contract, his role, profile settings, user location (the country where the request has come from) and settings of the testing environment (server which the user is connected through), there may be loaded eighteen different pages. In this case, you should override the method eighteen times to get all the pages, and after some time you should maintain all these versions of the method if something has been changed in the application. Of course, you can use a generic method aka <T>Login(username, password) in your tests but we’re talking about a maximum ease of writing and understanding of tests. It’s hard to imagine that a customer or PM or manual tester at least superficially understand the generics.

The second underwater stone is the scaling. If you described a page or some of its part as a single object and one day something is changed of this page (or its part) you will have to update the object itself, its methods (there may be a lot of ones) and related tests (the number of such tests may be actually huge). In general, the causes from above are already sufficient to decide not to implement the Page Object pattern to web pages or forms but to implement the pattern to page’s elements. In my approach, an object I will interact with is a web element, not the entire page. You may call it Element Object. I describe every single web page (or web form) as a separate class and it still contains a set of web elements located on the related web page but there are no methods for interaction with its elements. Instead, the web elements (buttons, links, images, tables, etc.) are described as the page’s properties. Every element is defined in its own class and has its own methods for interaction. Thus, the Page Object pattern is implemented in relation to web elements, not web pages.

Please, keep in mind that the bigger and complex object is the more difficult is its maintenance. You can describe, for example, the entire login form as a single entity or you can describe separately every element of this form as a single entity. The second approach will be more flexible.

Example:

public class Login
{
   //the class contains a set of properties, not methods
   //this is full description of login form. No additional verification, actions or logging is required.Everything, together with all properties and methods of every element, is encapsulated inside appropriate web element class
   public Textbox Textbox_UserName { get { return new Textbox(“locator of the element on the page”, [other parameters]); } }
   public Textbox Textbox_Password { get { return new Textbox(“locator of the element on the page”, [other parameters]); } }
   public Textbox Button_LogIn { get { return new Button(“locator of the element on the page”, [other parameters]); } }
}
[Test]
public void TestLogIn()
{
   TestedApplication.Page_Login.Textbox_UserName.TypeText(userName);
   TestedApplication.Page_Login.Textbox_Password.TypeText(password);
   TestedApplication.Page_Login.Button_Login.Click();
   Assert.IsTrue(TestedApplication.Page_StartPage.IsLoaded);
}
  • 1
  • 2