Overview:
I have been running thousands of automated regression test cases in multiple test environments for years. As part of CI / CD pipeline, I run the tests in QA, UAT, Staging and PROD. In this article, I would like to show you the approach I follow to make the tests run on any given test environment.
Many of us tend to write the scripts as shown below with a lot of variables and if-else blocks which is very difficult to maintain.
if(environment.equals("prod")){
url="http://testautomationguru.com";
username="tag";
password="password";
}else if(environment.equals("qa")){
url="http://qa.testautomationguru.com";
username="qatag";
password="qapassword";
}else if(environment.equals("dev")){
url="http://dev.testautomationguru.com";
username="devtag";
password="devpassword";
}
driver.get(url);
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
Now adding few environment details, say DB connections details to this script is not an easy task! It requires a lot of code change and testing to ensure that the script is not broken.
Keeping Environment Specific Data:
In order to make the script work for any given environment, We should avoid using any hard coded environment specific details in the script. As part of this article, Lets assume, We would need below details to run the script.
- application URL
- admin username
- admin password
- database host /Â IP address
- database port
- database username
- database password
I would suggest you to keep the environment specific details completely away from the test data as It is not going to change for each test. So, Lets create a property file as shown here.
# application properties
url=http://testautomationguru.com
username=tag
password=password123
# databsse properties
db.hostname=db.testautomationguru.com
db.port=3306
db.username=dba_admin
db.password=secured!
Lets maintain a separate property file for each environment as shown here.
Accessing Environment Specific Data:
To access environment specific data, I will be using Java – Owner library.
- Add the below dependency in your Maven project.
<dependency>
<groupId>org.aeonbits.owner</groupId>
<artifactId>owner</artifactId>
<version>1.0.8</version>
</dependency>
- Create an Interface as shown here.
@Sources({
"classpath:qa.properties" // mention the property file name
})
public interface Environment extends Config {
String url();
String username();
String password();
@Key("db.hostname")
String getDBHostname();
@Key("db.port")
int getDBPort();
@Key("db.username")
String getDBUsername();
@Key("db.password")
String getDBPassword();
}
- If the name of the method matches with the key of the property file, we can just call the method to access the value. (For ex: the method url() will refer to the key url in the property file)
- If the name of the method does not match with key in the property file, then we need to use @Key explicitly. (For ex: getDBPassword() will fetch the value of db.password by using @Key in the above Interface.)
- By using ConfigFactory, we create an instance of the Environment interface & access the property file.
Environment testEnvironment = ConfigFactory.create(Environment.class);
// prints http://qa.testautomationguru.com
System.out.println(testEnvironment.url());
// prints qa.db.testautomationguru.com
System.out.println(testEnvironment.getDBHostname());
- Now throughout your test and page objects, you would be using the testEnvironment object.
public class EnvironmentTest {
Environment testEnvironment;
@Test
public void functionalTest() {
System.out.println(testEnvironment.url());
System.out.println(testEnvironment.getDBHostname());
System.out.println(testEnvironment.getDBPassword());
}
@BeforeTest
public void beforeTest() {
testEnvironment = ConfigFactory.create(Environment.class);
}
}
- There is a small issue with the above approach that the environment is hard coded to read the qa.properties file. We could easily fix this by using a variable as shown here.
@Sources({
"classpath:${env}.properties"
})
public interface Environment extends Config {
String url();
String username();
String password();
@Key("db.hostname")
String getDBHostname();
@Key("db.port")
int getDBPort();
@Key("db.username")
String getDBUsername();
@Key("db.password")
String getDBPassword();
}
- Now we could pass the environment name as a variable to the test as shown here.
<suite name="TAG Suite">
<parameter name="environment" value="prod"/>
<test name="Simple example">
<-- ... -->
- TestNG test should access the environment parameter from the suite.xml and use it to read the specific property file.
public class EnvironmentTest {
Environment testEnvironment;
@Test
public void functionalTest() {
System.out.println(testEnvironment.url());
System.out.println(testEnvironment.getDBHostname());
System.out.println(testEnvironment.getDBPassword());
}
@BeforeTest
@Parameters({"environment"})
public void beforeTest(String environemnt) {
ConfigFactory.setProperty("env", environemnt);
testEnvironment = ConfigFactory.create(Environment.class);
}
}
Summary:
Adding any new environment specific data is very easy with this approach. Your tests and page objects remain unchanged. We need to update only Environment interface.
You can run the test against any given environment Dev, QA, UAT, Pre-Production, PROD without any code change. Name of the environment can be passed to the test as a parameter as shown above.
Happy Testing & Subscribe 🙂
Thanks, useful for me!
Glad that you found it useful.
hi vlns, can you integrate Search function for this site?
Thanks Dam…It is done.
Too bad I never realized that 🙂
Thanks! I found it very useful. Can you please share the Config class that you use here.
Config is a marker interface. You might have to import org.aeonbits.owner.Config;
hi vlns, thanks man.
In Class ConfigFactory, I have had problems with ConfigFactory.create(Environment.class)
Do you have function create ?
It does have a create method. Your environment class should extend Config marker interface.
Thanks for the information it is helping code for us.
This is very useful.
How are you taking care of the Jenkins jobs? Do you have parameters in those jobs /different jobs/run everything sequentially ?
I need more info to clarify better. But the idea here is to pass the information of the test environment as Jenkins parameter.
Trying this line – @BeforeTest
@Parameters({“environment”})
public void beforeTest(String environemnt) {
ConfigFactory.setProperty(“env”, environemnt);
testEnvironment = ConfigFactory.create(Environment.class);
}
I am not getting the method “create” in the eclipse suggestion.
This is what i am getting instead ;
ConfigFactory.create(clazz, imports)
This is the error i am getting when i try to implement –
Environment testEnvironment = ConfigFactory.create(Environment.class);
Bound mismatch: The generic method create(Class, Map…) of type ConfigFactory is not applicable for the arguments (Class). The inferred type Environment is not a valid substitute for the bounded parameter
Thanks. It is so useful for me.
sweet! looking forward to giving this a try. thanks for sharing!
OMG!!! The famous Angie Jones visited my blog. This made my year 🙂 Thanks Angie. I am huge fan of your works!
very helpful!!
This is amazing! I’ve been trying to find a way of handling properties files a lot easier. This makes it so much easier to manage. Thank you. 🙂