Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

8 Actionable Insights To Write Better Automation Code

DZone 's Guide to

8 Actionable Insights To Write Better Automation Code

These tips and insights will make your automated code cleaner and more robust. Just in time for the new year.

· DevOps Zone ·
Free Resource

As you start on with automation you may come across various approaches, techniques, framework, and tools you may incorporate in your automation code. Sometimes such versatility leads to greater complexity in code than providing better flexibility or better means of resolving issues. While writing an automation code it’s important that we are able to clearly portray our objective of automation testing and how are we achieving it. Having said so it’s important to write "clean code" to provide better maintainability and readability. Writing clean code is also not an easy cup of tea, you need to keep in mind a lot of best practices. The below topic highlights 8 silver lines one should acquire to write better automation code.

1. Naming Convention

This is indeed one of the rules of thumb to keep in mind as we move from manual to automation, or in fact writing code in any programming language. Following proper naming conventions helps in easier understanding of code and maintenance. This naming convention implies variables, methods, classes, and packages. For example, your method name should be specific as what it is intended for. A Register_User() method portrays the method displaying user registration within that method. Clearly defined method names adds to the easy maintenance and readability of the script. The same extends to the variable naming. I have noticed many people mentioning variables as a, b, c, and on, or even Web Elements as Weblelement1 and Webelement2 and so on. This gives no clue to the user seeing the variable name as what it intends to do.

Below is an example showing when naming goes wrong:

public void Register_User() throws InterruptedException 
{ 
driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS); 
driver.get("https://www.lambdatest.com/ "); 
driver.manage().window().maximize(); 
WebElement web1= driver.findElement(By.xpath("//a[text()='Free Sign Up']")); 
web1.click(); 
WebElement web2=driver.findElement(By.xpath("//input[@name='organization']")); 
web2.sendKeys("LambdaTest"); 
WebElement web3=driver.findElement(By.xpath("//input[@name='first_name']")); 
web3.sendKeys("Test"); 
WebElement web4=driver.findElement(By.xpath("//input[@name='last_name']")); 
web4.sendKeys("User"); 
WebElement web5=driver.findElement(By.xpath("//input[@name='email']")); 
web5.sendKeys("sadhvi.singh@navyuginfo.com"); 
WebElement web6=driver.findElement(By.xpath("//input[@name='password']")); 
web6.sendKeys("TestUser123"); 
WebElement web7=driver.findElement(By.xpath("//input[@name='phone']")); 
web7.sendKeys("9412262090"); 
WebElement web8=driver.findElement(By.xpath("//button[text()='SIGN UP']")); 
web8.click(); 
Thread.sleep(3500); }


The above code shows how method1  gives no clue to the user as what this method exactly does. Also, all web elements are denoted via web1, web2…and so on. User cannot identify which web element captures which field.

A correct way of representation can be marked as follows for the same above code:

public void Register_User() throws InterruptedException 
{ 
driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS); 
driver.get("https://www.lambdatest.com/ "); 
driver.manage().window().maximize(); 
WebElement link= driver.findElement(By.xpath("//a[text()='Free Sign Up']")); 
link.click(); 
WebElement organization=driver.findElement(By.xpath("//input[@name='organization']")); 
organization.sendKeys("LambdaTest"); 
WebElement first_name=driver.findElement(By.xpath("//input[@name='first_name']")); 
first_name.sendKeys("Test"); 
WebElement last_name=driver.findElement(By.xpath("//input[@name='last_name']")); 
last_name.sendKeys("User"); 
WebElement email=driver.findElement(By.xpath("//input[@name='email']")); 
email.sendKeys("sadhvi.singh@navyuginfo.com"); 
WebElement password=driver.findElement(By.xpath("//input[@name='password']")); 
password.sendKeys("TestUser123");
WebElement phone_number=driver.findElement(By.xpath("//input[@name='phone']"));
phone_number.sendKeys("9412262090");
WebElement button=driver.findElement(By.xpath("//button[text()='SIGN UP']")); 
button.click(); Thread.sleep(3500); String url= driver.getCurrentUrl(); 
assertEquals("fail- unable to register", url, "https://accounts.lambdatest.com/user/email-verification"); 
}


Here the method name Register_User   clearly defines user through name indicating this method contains code related to the registration of the user. Similarly, all web elements or variables are provided with names which relate to the captured fields used for the defined intent.

Usually, using camel casing for writing down methods or variables is usually encouraged for its better clarity in terms of readability and maintaining the script.

2. The Three R’s: Reduce, Reuse and Recycle

It’s important to ensure your methods are broken to the smallest chunks of user scenarios. They should cover simple and single flows. Do not overcomplicate your methods with multiple functionalities covered in a single method. For example, a login feature needs the user to be registered on the application. Keep your register feature into another method and if required, call that method in your login method. Reducing the complexity of the methods leads to easy maintainability of the code.

Also, reuse your methods wherever required; do not copy and paste the same code in different methods. This leads to unnecessary duplication and redundancy in the code. Increasing the lines of code does not means you have written a good code. Refactoring and optimizing your code is a key to a writing stable, robust and better automation code.

Recycling is also another useful tip for writing better automation code. I have experienced people who automate the legacy system, do not tend to change the existing method in the automation framework and rewrite another method whenever there is a change in existing functionality. This simply makes the framework as brittle. Always update the existing methods whenever the flow changes, though it has its own challenges, where the new user may not be aware of the dependencies the method may have, but I believe we should always counter things for the longer perspective than achieving those shorter goals.

Below is an example of how the login code is being simplified into a small chunk of functionality and another registration method is been used for easier simplification of the whole process.


@Test 
public void Register_User() throws InterruptedException 
{ 
driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS); 
driver.get("https://www.lambdatest.com/ "); 
driver.manage().window().maximize(); 
WebElement link= driver.findElement(By.xpath("//a[text()='Free Sign Up']")); 
link.click(); 
WebElement organization=driver.findElement(By.xpath("//input[@name='organization']")); 
organization.sendKeys("LambdaTest"); 
WebElement first_name=driver.findElement(By.xpath("//input[@name='first_name']")); 
first_name.sendKeys("Test"); 
WebElement last_name=driver.findElement(By.xpath("//input[@name='last_name']")); 
last_name.sendKeys("User"); 
WebElement email=driver.findElement(By.xpath("//input[@name='email']")); 
email.sendKeys("sadhvi.singh@navyuginfo.com"); 
WebElement password=driver.findElement(By.xpath("//input[@name='password']")); 
password.sendKeys("TestUser123");
WebElement phone_number=driver.findElement(By.xpath("//input[@name='phone']")); 
phone_number.sendKeys("9412262090"); 
WebElement button=driver.findElement(By.xpath("//button[text()='SIGN UP']")); 
button.click(); } @Test public void Login_User() 
{     
driver.get("https://accounts.lambdatest.com/login");   
driver.findElement(By.xpath("//input[@name='email']")).sendKeys("User2@gmail.com");   
driver.findElement(By.xpath("//input[@name='password']")).sendKeys("TestUser123");   
driver.findElement(By.xpath("//button[@class='sign-up-btn']")).click(); 
}   
@AfterClass public static void BrowserClose() 
{ 
driver.quit(); 
}   
}


3. Structure Your Tests Well

Well, this is indeed one of the major actionable insights to ensure better automation code. It is not only easy to understand but does not take much effort in maintenance. Structuring your tests with the help of  a framework adds value to your work and reduces the maintenance effort in the long run. You can control the flow of your application via the use of annotations provided by frameworks like JUnit and TestNG. For example, using an annotation like @BeforeClass   can help you direct your time-intensive activities like connecting to the database, setting up the browser-related code in this method with @BeforeClass   annotation associated with it. This helps an automation tester known right away exactly what that method does and when it is called. Just imagine your setting up process is clear and sorted out from the other pieces of your code. Similarly, an @AfterClass   annotation helps you perform clean up activities like disconnecting to the database, closing your current browser sessions, etc.

Below is an example highlighting a better structuring approach is been shown through the TestNG framework:

import static org.junit.Assert.*;   
import java.util.concurrent.TimeUnit;   
import org.openqa.selenium.By; 
import org.openqa.selenium.WebDriver; 
import org.openqa.selenium.WebElement; 
import org.openqa.selenium.chrome.ChromeDriver; 
import org.testng.annotations.AfterClass; 
import org.testng.annotations.BeforeClass; 
import org.testng.annotations.Test;   
public class Lamdatest 
{   
static WebDriver driver; 
@BeforeClass 
public static void BrowserOpen() 
{ 
System.setProperty("webdriver.chrome.driver", "chromepath");     
driver= new ChromeDriver() ;     
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS); } 
@Test(priority=1) 
public void Register_User() throws InterruptedException { driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS); driver.get("https://www.lambdatest.com/ "); 
driver.manage().window().maximize(); 
WebElement link= driver.findElement(By.xpath("//a[text()='Free Sign Up']")); 
link.click(); 
WebElement organization=driver.findElement(By.xpath("//input[@name='organization']")); 
organization.sendKeys("LambdaTest"); 
WebElement first_name=driver.findElement(By.xpath("//input[@name='first_name']")); 
first_name.sendKeys("Test"); 
WebElement last_name=driver.findElement(By.xpath("//input[@name='last_name']")); 
last_name.sendKeys("User"); 
WebElement email=driver.findElement(By.xpath("//input[@name='email']")); 
email.sendKeys("sadhvi.singh@navyuginfo.com"); WebElement password=driver.findElement(By.xpath("//input[@name='password']")); 
password.sendKeys("TestUser123"); 
WebElement phone_number=driver.findElement(By.xpath("//input[@name='phone']")); 
phone_number.sendKeys("9412262090");
WebElement button=driver.findElement(By.xpath("//button[text()='SIGN UP']")); 
button.click(); 
String url= driver.getCurrentUrl(); 
assertEquals("fail- unable to register", url, "https://accounts.lambdatest.com/user/email-verification"); 
} 
@Test(dependsOnMethods="Register_User") 
public void Login_User() {     
driver.get("https://accounts.lambdatest.com/login");   
driver.findElement(By.xpath("//input[@name='email']")).sendKeys("User2@gmail.com");   
driver.findElement(By.xpath("//input[@name='password']")).sendKeys("TestUser123");   
driver.findElement(By.xpath("//button[@class='sign-up-btn']")).click(); 
} @AfterClass public static void BrowserClose() { driver.quit(); }   
}


Making a decision of what annotations should be associated with which test method is important. With clear dependencies and priorities defined the tests and code can be structured based on the flow of the application.

4. Thorough Validation Of Your Tests

Being a QA, you know it is all about validating your expected and actual meets, and the same stands for your automation code. If your script does not talk in terms of validation, creating one will never make sense nor be of any use. Ideally, every user action should be validated as are your test case steps, whether it is validating the visibility of an element, typography, textual representation, redirections to a page, or any kind of visual validation or even if it’s about evaluating the results from the database.

Even if your validation fails to make sure, the failure message is also displayed so that one can find out what went wrong. The biggest mistake we make in terms of validating our code is writing from the terms of ensuring the validation is passed. We never contemplate what may happen if the code fails or does not perform the expected, what would be needed to proceed ahead.

If you wish to break the test as soon as your validation fails and jump to the other test one can use hard assertions, whereas if you wish to validate multiple checks on the same page, one can opt for soft assertions. To decide which assertion to use completely depend upon the use case.


//validate user able to login with valid credentials     
@Test public void Login_User() throws IOException {         
driver.get("https://accounts.lambdatest.com/login");             
driver.findElement(By.xpath("//input[@name='email']")).sendKeys("User2@gmail.com"); 
driver.findElement(By.xpath("//input[@name='password']")).sendKeys("TetsUser123");     
driver.findElement(By.xpath("//button[@class='sign-up-btn']")).click();   
WebDriverWait wait= new WebDriverWait(driver, 15);                
wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.xpath("//a[@class='user-profile dropdown-toggle']"))));   String Current_url= driver.getCurrentUrl();   
Assert.assertEquals("https://accounts.lambdatest.com/user/email-verification", 
Current_url);   
System.out.println("user logged in sucesfully");   
driver.findElement(By.xpath("//a[@class='user-profile dropdown-toggle']")).click();   
driver.findElement(By.xpath("//a[contains(text(),'Logout')]")).click();     
} //validate user is unable to login with invalid credentials         
@Test 
public void Login_invalid_User() throws IOException {     
driver.get("https://accounts.lambdatest.com/login");   
driver.findElement(By.xpath("//input[@name='email']")).sendKeys("User21@gmail.com");   
driver.findElement(By.xpath("//input[@name='password']")).sendKeys("TestUser123");   
driver.findElement(By.xpath("//button[@class='sign-up-btn']")).click();   
WebDriverWait wait= new WebDriverWait(driver, 15);   
String str= driver.findElement(By.xpath("//p[@class='error-mass']")).getText();   
String Current_url= driver.getCurrentUrl();   
Assert.assertEquals("https://accounts.lambdatest.com/login", Current_url);   
System.out.println(str); }


There could be different approaches of covering your multiple validation checks: either you can opt to make a different method for each validation like I did above, or you can choose to make all the validations in a single method under the try-catch block.

5. Sleep Does Not Improve Stability

The biggest myth we tend to believe, especially when we are new to automation, is that providing an ample amount of wait to our script necessary or unnecessary will lead to executing our script smoothly. On the contrary, it makes our script flaky, and increases the overall execution time. The major problem with this type of static sleep is that we are not aware of the load of the machine on which tests are run and hence these may lead to timeouts. Therefore, thread.sleep should be avoided for maintaining better automation code. A better approach of using wait to your scripts is through condition binding, wherein the script can wait like a human till a certain condition is met. For example, waiting till the certain element is visible or not.

The explicit and fluent wait is more adaptable as an option to develop better automation code.

6. Making Your Tests Data Driven

Testing becomes more effective when tested across multiple forms of data, and the same is true when writing better automation code for testing a web application or any other software. In automation, the key is to test your test code through multiple forms of data rather than writing different test scripts for each of those data. This is easily achieved via a data-driven testing framework. It helps to store the test data input into an external database such as CSV files, Excel files, text files, XML files or even ODBC repositories. This data is called into your scripts and run across the same test code again and again. This helps reduce redundancy and faster execution in comparison to manual efforts. This approach makes your tests more realistic as you always have the advantage of changing your test data and running it over and over again on the same test code, thereby helping discover new bugs. Another benefit of this approach is it leads to the reduction in the number of test scripts you may have to add, speeding up your test cycles.

Keeping in pace with it, it also helps in easy maintainability of the scripts. All hardcoded values in your code tend to break in case of any application changes. An easier way to achieve this is to make all your hardcoded components variable-driven. For example, all locators can be kept out of the code, by storing their respective values in an Excel sheet and calling them in your script. In case any of your locators get broken, one needs to just change the locator value in the Excel and need not to touch the script at all.

A basic example of data-driven testing is:

@Test public void Login_User() throws IOException 
{          
File f1= new File("C://Users//navyug//Desktop//Test.xlsx");     
FileInputStream scr= new FileInputStream(f1);     
XSSFWorkbook book= new XSSFWorkbook(scr);     
XSSFSheet sheet=book.getSheetAt(0);     
for(int i=0; i<=sheet.getLastRowNum(); i++ )     
{     
//XSSFCell cell= sheet.getRow(i).getCell(1); 
Row row = sheet.getRow(i); Cell cell = row.getCell(0);     
driver.findElement(By.xpath("//input[@name='email']")).sendKeys(cell.toString());   
cell= row.getCell(1);      
driver.findElement(By.xpath("//input[@name='password']")).sendKeys(cell.toString());               
driver.findElement(By.xpath("//button[@class='sign-up-btn']")).click();   
WebDriverWait wait= new WebDriverWait(driver, 15);                
wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.xpath("//a[@class='user-profile dropdown-toggle']"))));   String Current_url= driver.getCurrentUrl();   
Assert.assertEquals("https://accounts.lambdatest.com/user/email-verification", Current_url);   
System.out.println("user logged in sucesfully");   
takescreenshot();   driver.findElement(By.xpath("//a[@class='user-profile dropdown-toggle']")).click();   driver.findElement(By.xpath("//a[contains(text(),'Logout')]")).click();     } }


The above code shows data been pulled from Excel for different login credentials. The same can be extended for Xpaths also where the XPath values can be pulled from excel also. Here the key point to address through data-driven approach is to remove the hardcoded values from our code, making it variable-oriented and along with it running the same piece of code across multiple sets of inputs.

7. Don’t Skip Out On Reporting!

Automation code won’t do any good if it does not report the result to you. In order to optimize your work as an automation engineer, it’s important to know which test code passed and which failed accompanied with screenshots. The best ROI you can show your stakeholder is via reporting. Sharing those detailed reports provide visibility and reduce your time on verification of your test execution scripts. You can achieve reporting through various techniques like TestNG HTML report generation, JUnit report generation, or via using an extent library.

The below code shows an example where post completion of the login functionality a screenshot has been taken as a proof of validation pass and below is a sample of the TestNG report generated post-execution:

//validate user able to login with valid credentials    
@Test public void Login_User() throws IOException {          
driver.get("https://accounts.lambdatest.com/login");             
driver.findElement(By.xpath("//input[@name='email']")).sendKeys("User2@gmail.com"); 
driver.findElement(By.xpath("//input[@name='password']")).sendKeys("TetsUser123");     
driver.findElement(By.xpath("//button[@class='sign-up-btn']")).click();   
WebDriverWait wait= new WebDriverWait(driver, 15);                
wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.xpath("//a[@class='user-profile dropdown-toggle']"))));   String Current_url= driver.getCurrentUrl();   
Assert.assertEquals("https://accounts.lambdatest.com/user/email-verification", Current_url);   
System.out.println("user logged in sucesfully");   
takescreenshot();   driver.findElement(By.xpath("//a[@class='user-profile dropdown-toggle']")).click();   driver.findElement(By.xpath("//a[contains(text(),'Logout')]")).click();     
}          
public void takescreenshot() throws IOException { TakesScreenshot scr= ((TakesScreenshot)driver);     
File file1= scr.getScreenshotAs(OutputType.FILE);         
FileUtils.copyFile(file1, new File("C:\\Users\\navyug\\Desktop\\Login_user.PNG")); 
}


8. Don’t Forget Cross Browser Testing!

All web applications today support multiple browsers and versions. It’s important that your code should target multiple browsers rather than making them for a specific browser. Running code on a specific browser takes away the cross-browser compatibility of your application. Perform cross-browser testing to ensure your applications offers seamless user experience across all the major browsers, we can extend our automation for this testing. Frameworks like TestNG helps to easily execute a test across various browsers.

Below is a code displaying how to run automation code on multiple browsers via TestNG:

public class crowssbrowser 
{ 
static WebDriver driver;     
@Parameters("browser") 
@BeforeClass 
public static void Browser_Select(String browser) 
{ if(browser.equalsIgnoreCase("firefox")) 
{   
System.setProperty("webdriver.firefox.marionette", "geckodriverpath");   
driver = new FirefoxDriver();     // If browser is IE, then do this       
}else if (browser.equalsIgnoreCase("chrome")) 
{     // Here I am setting up the path for my IEDriver     
System.setProperty("webdriver.chrome.driver", "chromedriverpath");     
driver= new ChromeDriver() ;   
}     
driver.get("https://accounts.lambdatest.com/login");   
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); 
}     
XML code:   <?xml ve rsion="1.0" encoding="UTF-8"?> 
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> 
<suite name="Suite" parallel="none">
 <test name="FirefoxTest"> 
<parameter name="browser" value="firefox" /> 
<classes> 
<class name="crowssbrowser" />
</classes> 
</test> 
<test name="chrometest"> 
<parameter name="browser" value="chrome" /> 
<classes> 
<class name="crowssbrowser" /> 
</classes> 
</test> 
</suite>


The above code shows a method which takes a browser as a parameter where different browsers drivers are set up. Using TestNG XML file we have passed parameters as different browsers on which the code will run through for the login feature on both firefox and chrome.

LambdaTest is a cross-browser testing tool which provides a Selenium grid consisting 2000+ browsers. The Selenium grid at LambdaTest provides support to frameworks related to various programming languages, so you can speed up your continuous integration and continuous delivery.

That was all from my end. I hope these tips will serve as useful actionable insights for writing better automation code. Feel free to share the tips that helped you deliver better automation code. Cheers!

Topics:
automation testing ,tips and techniques ,clean code ,devops ,test driven development

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}