Using Selenium to Test Gen™ Web Applications
Gen has the capability to produce applications for a number of platforms and in multiple languages. As a result, it is difficult to create a set of unit tests to ensure that the individual pieces of the application work as the developer expects them to. As with using Pywinauto and Python to test Windows GUI applications, the Gen team has developed a methodology using Selenium browser automation to automate the testing of Web-based applications that have been generated by Gen. Selenium is available for both Python and Java, but the discussion here will use Java for its examples.
Get to know your Web page
The first step to automating a Web-based Gen application is reviewing the generated Web page in your browser. Both Google Chrome and Microsoft Edge provide an inspect capability either from the context menu, by pressing F12, or as Browser Developer Tools. The image below shows the inspector screen for a Gen model using Chrome.
The inspector display can help determine the best way to access the various elements on the page. Selenium provides a number of ways that can be used to find the element. These include:
- Class Name — Find the first element on the page that is a member of the given class. For example, to find the first Group Box in the picture by class:
driver.findElement(By.className("DFGUIGBXline GroupBox1"));
- Id — Find the element with the given id. To find the same group box by Id:
driver.findElement(By.id("GroupBox1"));
- Name — Find the element with the given name. On Gen-generated Web pages, the name and id are often the same, so the following looks very similar to the line up above:
driver.findElement(By.name("GroupBox1"));
- XPath — Locating by xpath searches the tree of web page elements to find a specific element. This approach is guaranteed to find the specific desired element, but it is slower than the other location methods, and may not find the correct element if the page layout changes. The xpath locator for the same group box is:
driver.findElement(By.xpath("/html/body/fieldset[1]"));
Once you have found the elements that you need to automate, the next step is to determine what actions need to be performed on each one. For example, a button may need to be clicked or a text field may need to be written to or read from.
Acting on Your Web Page
Selenium provides functionality on the WebElement class to handle common automation tasks. Among the methods are:
- click() — Left clicks the item. This event does not wait for the outcome of the click, so it may be necessary to insert a sleep into your test to allow time for the click to be processed. If the click causes a new page to load, you will lose the reference to the clicked item.
- clear() — Clear a text field. This method has no effect on non-text items. Also, the clear method just clears the text; it is not the same as using the delete or backspace key.
- sendKeys(keysToSend) — This method sends keystrokes to the selected Web element. The parameter is a character sequence, which includes regular strings, but also handles special keys like Control and Shift.
- getAttribute(attributeName) — This method gets the current value of an attribute of the Web Element. The attribute “value” will return the contents of a text field. The attribute “style” will return a string that represents the style of the element. This can be useful if your application changes the style of an element based on system behavior.
- getRect() — The getRect method can be used to determine the size and position of an element.
- findElements(by) — This method will find sub-elements of an object using the locators described above. This can be useful for counting the rows or columns of a table.
- findElement(by) — The findElement method will return a specific sub-element of an object using the locators described above. This can be useful for getting the value of a cell in a table.
Along with methods on the individual web elements, Selenium provides classes to handle special cases like alerts or special mouse click sequences.
To handle pop-ups on your Web page, Selenium provides Alerts. There are a number of functions associated with alerts that are important for automation testing.
- To access the alert, use the following:
Alert alert = driver.switchTo().alert();
- getText() — This method retrieves the contents of an alert message. This can be extremely helpful for checking that error conditions have proper messages.
- sendKeys(keysToSend) — This call will set the text for a message box that requires input.
- accept() — This method is equivalent clicking the OK button on the pop-up.
- dismiss() — This method is equivalent to just closing the pop-up or clicking the Cancel button on pop-ups with multiple choices.
Sometimes it is necessary to perform an action on a Web Element that is more complicated than a simple left click or typing text. Selenium provides Action classes to handle these situations.
- To use special actions, the following code is needed to set up the Actions object:
Actions actions = new Actions(driver);
- Action methods can be chained together, but the last call must be perform() to cause Selenium to perform the desired activities.
- keyDown(key) — This method represents pushing a key down. Examples might be the shift key or the ctrl key. Selenium has a Keys class that defines the special keyboard keys.
- keyUp(key) — This is the reverse of keyDown and represents releasing the key.
- sendKeys(keysToSend) — This method sends a key sequence to the Web page.
- sendKeys(element, keysToSend) — This version of sendKeys will send the keystrokes to the selected element.
- clickAndHold(element) — This method represents clicking the left mouse button on the selected element and holding the button down.
- click(element) — This method represents a standard click on a Web element.
- contextClick(element) — This method represents a right button click on the selected element.
- doubleClick(element) — This function sends a double left click to the selected Web element.
- moveToelement(element) — This method will move the mouse to the center of the specified Web element.
Selenium provides additional actions like drag and drop, mouse wheel movement, and touch screen. Details about these methods can be found in the Selenium documentation.
Assemble your Tests
Once you have found all the elements on a page and determined the actions that need to be performed, it is time to assemble everything into your testing framework.
On the Gen development team, we have been using TestNG as our testing framework, and examples will be based on that. However, all testing frameworks have similar structures, so these concepts should apply regardless of the one you choose
The first step is to initialize the tests. The first example shows this for Chrome.
private WebDriver driver;
private MainPage page;
@BeforeClass
@Parameters({"url"})
public void setupClass(String url) {
System.setProperty("webdriver.chrome.driver",
"<path to driver>\chromedriver.exe");
driver = new ChromeDriver();
driver.get(url);
page = new MainPage();
}
This second example shows the set up for an Edge browser.
private WebDriver driver;
private MainPage page;
@BeforeClass
@Parameters({"url"})
public void setupClass(String url) {
System.setProperty("webdriver.edge.driver",
"<path to driver>\msedgedriver.exe");
driver = new EdgeDriver();
driver.get(url);
page = new MainPage();
}
TIP: The browser drivers are provided by Selenium, and it is important that they get updated every time there is a new version of the browser.
The next step is to create a class for each Web page and accumulate all the elements for the page into that class. We have found that this makes the tests easier to understand and maintain. In the example code below, we called the class MainPage. Here is the code to implement the main page of the example model shown above.
package com.ca.gen.auto.ablk36.pageobjects.java;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import com.ca.gen.auto.ablk36.pageobjects.MainPage;
import com.ca.gen.auto.base.Base;
import com.ca.gen.auto.utils.TestUtil;
public class MainPage {
private static final String VALUE = "value";
@FindBy(id = "Button1") private WebElement btnTest;
@FindBy(id = "STATUS1") private WebElement txtImplicitStatus;
@FindBy(id = "COUNT1") private WebElement txtImplicitPopSize;
@FindBy(id = "COUNT2") private WebElement txtImplicitRefSize;
@FindBy(id = "STATUS2") private WebElement txtExplicitStatus;
@FindBy(id = "COUNT3") private WebElement txtExplicitPopSize;
@FindBy(id = "COUNT4") private WebElement txtExplicitRefSize;
@FindBy(id = "ERRMSG1") private WebElement txtTestStatus;
private WebDriver driver;
public MainPageJava(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(this.driver, this);
Thread.sleep(2000);
this.driver.switchTo().frame("AppWindow");
}
public void clickBtnTest() {
btnTest.click();
Thread.sleep(1000);
}
public String getTxtImplicitStatus() {
return txtImplicitStatus.getAttribute(VALUE);
}
public String getTxtImplicitPopSize() {
return txtImplicitPopSize.getAttribute(VALUE);
}
public String getTxtImplicitRefSize() {
return txtImplicitRefSize.getAttribute(VALUE);
}
public String getTxtExplicitStatus() {
return txtExplicitStatus.getAttribute(VALUE);
}
public String getTxtExplicitPopSize() {
return txtExplicitPopSize.getAttribute(VALUE);
}
public String getTxtExplicitRefSize() {
return txtExplicitRefSize.getAttribute(VALUE);
}
public String getTxtTestStatus() {
return txtTestStatus.getAttribute(VALUE);
}
public String getLessEqualTableCell(int row, int col) {
String xpath =
"//*[@id=\"ListBox3\"]/table/tbody/tr[" + (row + 1) + "]/td[" + col + "]";
return driver.findElement(By.xpath(xpath)).getText();
}
public String getGreaterTableCell(int row, int col) {
String xpath =
"//*[@id=\"ListBox4\"]/table/tbody/tr[" + (row + 1) + "]/td[" + col + "]";
return driver.findElement(By.xpath(xpath)).getText();
}
}
This example code shows many of the features discussed above. The FindBy annotation is equivalent to driver.findElement. In this case all of the Web elements have unique identifiers, so the code uses id as the locator key.
The constructor does a few things. First it attaches the class to the Web page using the PageFactory.initElements call. Then, to make sure everything has been loaded, it sleeps for 2 seconds before finally switching to the AppWindow frame to access the elements. In Gen Java applications, all pages have an AppWindow frame, so this should appear in all page classes.
The first function clicks the test button. It uses the FindBy annotated object — in this case the item with the id of Button1 — and clicks it.
The example Web page does not have any user input, so the next group of functions simply go to the expected page and return the value attribute to get the outputs from pressing the button.
The last two functions illustrate the benefits of using xpath. In these cases, we are looking for the item at a particular row and column in a table. The xpath uses id to get to the table and then drills down through the HTML to get to the specific row and column. The row number is incremented because the table has a header row, so the data actually starts on row 2 instead of row 1.
Build the Test
Now that the various page elements and their interactions have been defined, it is time to write the test. The code below is a sample test case for the Web page we have been discussing. For this test, the code will click the test button and then compare the results on the page with the expected values.
@Test
public void testRgv() {
page.clickBtnTest();
Assert.assertEquals(page.getTxtImplicitStatus(), "TEST SUCCEEDED.");
Assert.assertEquals(page.getTxtImplicitPopSize(), "50000");
Assert.assertEquals(page.getTxtImplicitRefSize(), "50000");
Assert.assertEquals(page.getTxtExplicitStatus(), "TEST SUCCEEDED.");
Assert.assertEquals(page.getTxtExplicitPopSize(), "50000");
Assert.assertEquals(page.getTxtExplicitRefSize(), "50000");
for (int row = 1; row <= 4; row++) {
Assert.assertTrue(page.getLessEqualTableCell(row, 1).toLowerCase().endsWith("succeeded"),
page.getLessEqualTableCell(row, 1));
Assert.assertEquals(page.getLessEqualTableCell(row, 2),
page.getLessEqualTableCell(row, 3));
}
for (int row = 1; row <= 4; row++) {
Assert.assertTrue(page.getGreaterTableCell(row, 1).toLowerCase().endsWith("succeeded"),
page.getGreaterTableCell(row, 1));
Assert.assertEquals(page.getGreaterTableCell(row, 2),
page.getGreaterTableCell(row, 3));
}
Assert.assertEquals(page.getTxtTestStatus(), "All tests succeeded.");
}
The first step is to click the Test button, which will load the page with data from the Gen application.
Following the click, the test looks at the data in the “Implicitly Indexed” group box. In this case, the status is expected to be “TEST SUCCEEDED” and the number of rows is expected to be 50000.
The next section of the test examines the “Explicitly Indexed” group box. Again, the expected result is “TEST SUCCEEDED” with 50000 rows sent and received.
The next two sections of the test look at list results. As described earlier, reading the contents of a list box uses an xpath reference. For this part of the test we loop over each row, making sure that the status (column 1) ends with the word succeeded. Then we make sure the number of records sent (column 2) matches the number of records received (column 3).
Conclusion
Web applications created using Gen can provide easy access to both mainframe and distributed back ends. It is important that they get tested thoroughly to make sure the business logic is correctly implemented. The Gen team has found that Selenium provides a good tool for automating the testing of Web applications. This allows us to integrate the testing into our DevOps environment. Because Gen can be used to generate GUI-based applications, we have looked at test frameworks for these as well. For testing Gen-based GUI applications, check out my blog: Automating Gen™ GUI Testing with Python.