So anyway Selenium can be written in quite a few programming languages, anyway I picked Java to write this. I am an expert programmer but still I like the way it can be formatted or written so everyone can understand. And I will try to explain this in this post, how to make a Selenium test more readable.
Why do we need to be much constructive and follow certain processes to make it much better?
Well, quite a familiar situation to some of the teams who are practising Agile newly, where a project is very agile, architecture is evolving, requirements are quite evolving, very unclear process, Agile is not followed by books etc there could be so many reasons for this to happen.
In this situation, development or business is still expecting the QA to automate all tests and integrate them in to a build process as a part of Continuous Integration environment etc. QA complains that the build is not stable and without the build being in a good stable condition there's no point in automating tests. WOW.. sounds familiar isn't it?
What're the basic things one should know about UI mapping technique?
Well, as you can imagine UI mapping technique is nothing but an OR, if you are using some traditional automation tools. Where you can store your objects and constantly refer them in your tests.
I would say, its not quite friendly. You have to do a bit of work to actually find properties you want selenium to use to recognize the objects, by using some open source tools/ add-ons (like, Firebug) or looking at the code or whatever.
so, how to Automate and still make the Maintainability a less effort?
1st Step, I would to make it more readable is by creating a very basic UI map. UI mapping is technique used to make the Web elements (Objects for QTP folks) properties all stored in a single place. It has the following advantages:
1. Any property change for a given web element (ID, Name, Link, CSS, XPATH etc) in the future is quite easy to make. Because there's only 1 place to go to make such a change, your UI map.
For traditional automation tool users, its like an Object repository (OR), where you will update or add Mandatory/ Assisted properties etc
2. Because we're using a single place to store and retrieve all object properties from, you will make a change to an Web element properties once for all.
3. You don't need to be an expert in some special technology or something. You can do it in your comfortable language. It's just a technique and there's no strict rules on how to create it, there's a very good guidance in Selenium documentation section
Okay, enough details. How do we actually do it?
Well, again it depends on few things like the size of the application, architecture of the application, number of modules, size of the each module and how much automation effort is planned for testing this application etc. So far I could see Selenium is used either for smoke testing or for some functional testing, where as it can do lot more.
Anyway, I used to java and I have used a Java file to store my web element mappings and I refer to these mapped elements throughout my test suite..
Well, a Java file where I stored my mappings will look something like below
public class gmailMap {
public String emailID = "id=Email";
public String passWord = "id=Passwd";
public String signInBtn = "id=signIn";
public String waitTime = "30000";
public String InboxCnt = "css=div.XQRo3.UKr6le";
public String emailTable = "//*[@id=\":pd\"]";
public String firstemailFrom = "id=:p9";
public String firstemailSubj = "id=:p6";
public String accName = "id=gbi4t";
public String signOut = "//*[@id=\"gb_71\"]";
}
and tests might look something like below when they use the UI mapping technique followed.
@Test
public void testGmailLogIn() throws Exception {
gmailMap UImap = new gmailMap();
selenium.open("/");
selenium.selectWindow(null);
selenium.type(UImap.emailID, "<<username>>");
selenium.type(UImap.passWord, "<<password>>");
selenium.click(UImap.signInBtn);
selenium.waitForPageToLoad(UImap.waitTime);
String newEmails = selenium.getText(UImap.InboxCnt);
System.out.println(newEmails);
verifyTrue(selenium.isElementPresent(UImap.emailTable));
String firstEmail = selenium.getText(UImap.firstemailFrom);
String fullSubjectText = selenium.getText(UImap.firstemailSubj);
String accName = selenium.getText(UImap.accName);
System.out.println(UImap.firstemailFrom);
System.out.println(UImap.firstemailSubj);
System.out.println(UImap.accName);
selenium.click(UImap.accName);
assertTrue(selenium.isElementPresent(UImap.signOut));
selenium.click(UImap.accName);
selenium.click(UImap.signOut);
selenium.waitForPageToLoad(UImap.waitTime);
selenium.selectWindow("null");
//selenium.click("id=PersistentCookie");
}
Here as you can see, there's only element names are used, suggesting the reader that I want to use "selenium" to "type", some text "username" in the control called "UImap.emailID".
Please note, the webElement name given in the UImap should be very understandable. Whatever it is, but naming convention is important too so the user can easily understand it by looking at.
There's a few different ways to do it, one of the other popular way of doing it, especially for people from Java world, is using the properties file. In this approach, you will use a simple .properties file where a key=value pairs of representation is used. So a simple gmailUImap.properties might look something like below
UI_Email="id=Email"
UI_passWord = "id=Passwd";
and your test might look something like below.. I am just writing it here, I didn't ran this myself, so if it doesn't run well, you can correct it or pass on your comments on this post as well.
public class gmailClass extends SeleneseTestCase{
public String PROP_FILE="gmailUI.properties";
public String UIemail;
public String UIpassWord;
@BeforeClass
public void setUp() throws Exception {
SeleniumServer selServ = new SeleniumServer();
selServ.boot();
setUp("https://accounts.google.com/", "*firefox");
selenium.windowMaximize();
selenium.windowFocus();
}
public gmailClass() throws Exception{
//create and load properties
try{
InputStream is = gmailClass.class.getResourceAsStream(PROP_FILE);
Properties prop = new Properties();
prop.load(is);
UIemail = prop.getProperty("UI_Email");
UIpassWord = prop.getProperty("UI_passWord");
is.close();
System.out.println(emailID);
/* code to use values read from the file*/
}catch(Exception e){
System.out.println("Failed to read from " + PROP_FILE + " file.");
}
}
@Test
public void testGmailLogIn() throws Exception {
gmailMap UImap = new gmailMap();
selenium.open("/");
selenium.selectWindow(null);
selenium.type(UIemail, "<<username>>");
selenium.type(UIpassWord, "<<password>>");
.
..
...
....
}
Whatever way you chose to write your tests, this process makes it a bit better readable test, also making it more maintainable. But, this is missing something, we're giving the precise data to run the test. So its obviously not so complete to data drive it, especially when we want to test it against positive, negative, boundary conditions and most importantly if you want to load test it.. this is our next step
3. You don't need to be an expert in some special technology or something. You can do it in your comfortable language. It's just a technique and there's no strict rules on how to create it, there's a very good guidance in Selenium documentation section
Okay, enough details. How do we actually do it?
Well, again it depends on few things like the size of the application, architecture of the application, number of modules, size of the each module and how much automation effort is planned for testing this application etc. So far I could see Selenium is used either for smoke testing or for some functional testing, where as it can do lot more.
Anyway, I used to java and I have used a Java file to store my web element mappings and I refer to these mapped elements throughout my test suite..
Well, a Java file where I stored my mappings will look something like below
public class gmailMap {
public String emailID = "id=Email";
public String passWord = "id=Passwd";
public String signInBtn = "id=signIn";
public String waitTime = "30000";
public String InboxCnt = "css=div.XQRo3.UKr6le";
public String emailTable = "//*[@id=\":pd\"]";
public String firstemailFrom = "id=:p9";
public String firstemailSubj = "id=:p6";
public String accName = "id=gbi4t";
public String signOut = "//*[@id=\"gb_71\"]";
}
and tests might look something like below when they use the UI mapping technique followed.
@Test
public void testGmailLogIn() throws Exception {
gmailMap UImap = new gmailMap();
selenium.open("/");
selenium.selectWindow(null);
selenium.type(UImap.emailID, "<<username>>");
selenium.type(UImap.passWord, "<<password>>");
selenium.click(UImap.signInBtn);
selenium.waitForPageToLoad(UImap.waitTime);
String newEmails = selenium.getText(UImap.InboxCnt);
System.out.println(newEmails);
verifyTrue(selenium.isElementPresent(UImap.emailTable));
String firstEmail = selenium.getText(UImap.firstemailFrom);
String fullSubjectText = selenium.getText(UImap.firstemailSubj);
String accName = selenium.getText(UImap.accName);
System.out.println(UImap.firstemailFrom);
System.out.println(UImap.firstemailSubj);
System.out.println(UImap.accName);
selenium.click(UImap.accName);
assertTrue(selenium.isElementPresent(UImap.signOut));
selenium.click(UImap.accName);
selenium.click(UImap.signOut);
selenium.waitForPageToLoad(UImap.waitTime);
selenium.selectWindow("null");
//selenium.click("id=PersistentCookie");
}
Here as you can see, there's only element names are used, suggesting the reader that I want to use "selenium" to "type", some text "username" in the control called "UImap.emailID".
Please note, the webElement name given in the UImap should be very understandable. Whatever it is, but naming convention is important too so the user can easily understand it by looking at.
There's a few different ways to do it, one of the other popular way of doing it, especially for people from Java world, is using the properties file. In this approach, you will use a simple .properties file where a key=value pairs of representation is used. So a simple gmailUImap.properties might look something like below
UI_Email="id=Email"
UI_passWord = "id=Passwd";
and your test might look something like below.. I am just writing it here, I didn't ran this myself, so if it doesn't run well, you can correct it or pass on your comments on this post as well.
public class gmailClass extends SeleneseTestCase{
public String PROP_FILE="gmailUI.properties";
public String UIemail;
public String UIpassWord;
@BeforeClass
public void setUp() throws Exception {
SeleniumServer selServ = new SeleniumServer();
selServ.boot();
setUp("https://accounts.google.com/", "*firefox");
selenium.windowMaximize();
selenium.windowFocus();
}
public gmailClass() throws Exception{
//create and load properties
try{
InputStream is = gmailClass.class.getResourceAsStream(PROP_FILE);
Properties prop = new Properties();
prop.load(is);
UIemail = prop.getProperty("UI_Email");
UIpassWord = prop.getProperty("UI_passWord");
is.close();
System.out.println(emailID);
/* code to use values read from the file*/
}catch(Exception e){
System.out.println("Failed to read from " + PROP_FILE + " file.");
}
}
@Test
public void testGmailLogIn() throws Exception {
gmailMap UImap = new gmailMap();
selenium.open("/");
selenium.selectWindow(null);
selenium.type(UIemail, "<<username>>");
selenium.type(UIpassWord, "<<password>>");
.
..
...
....
}
Whatever way you chose to write your tests, this process makes it a bit better readable test, also making it more maintainable. But, this is missing something, we're giving the precise data to run the test. So its obviously not so complete to data drive it, especially when we want to test it against positive, negative, boundary conditions and most importantly if you want to load test it.. this is our next step
If you have any comments or suggestions, feel free to pass them on!