Introduction
In my previous articles, Design Grid Control Automated Tests Part 1, Design Grid Control Automated Tests Part 2 and Design Grid Control Automated Tests Part 3, I started a mini-series about writing proper grid control's automated tests. This will be the fourth final part. Here, I am going to talk about how we can reuse to the maximum extent the logic that we have already created for asserting the controls.
Test Cases Reuse Problem
Here is how one existing grid control's automated test looks like:
[TestMethod]
public void OrderIdEqualToFilter()
{
this.driver.Navigate().GoToUrl(@"http://demos.telerik.com/kendo-ui/grid/frozen-columns");
var kendoGrid = new KendoGrid(this.driver, this.driver.FindElement(By.Id("grid")));
var newItem = this.CreateNewItemInDb();
kendoGrid.Filter(
OrderIdColumnName,
Enums.FilterOperator.EqualTo,
newItem.OrderId.ToString());
this.WaitForGridToLoad(1, kendoGrid);
var items = kendoGrid.GetItems<GridItem>();
Assert.AreEqual(1, items.Count);
}
The test case tests that the order id's column filter is working as expected. In real-world applications, many times we have grids that display almost the same data in a little different manner. For example, you can have one grid that displays the expired orders and one for the successfully completed. Both grids have almost identical columns. However, you need to automate both grids because usually a custom code is added to enable the desired behaviour such as filtering by status, etc.
Expired Orders Grid
Successfully Completed Orders Grid
Advanced Reuse Tactics
We will have almost identical tests, but sometimes some of the columns might be missing or new columns might be displayed so we need a solution where we can configure which column is to be verified or not. So my idea is to create different assert classes for each column. This way, through a composition in your tests, you can design-time choose which column is to be asserted or not.
IGridPage Interface
Until now, some of the tests worked with a single page or with the kendo grid controls directly. Nonetheless, in order to make the column's asserter more generic, it should work with an interface. Because of that, I have created the new IGridPage
interface.
public interface IGridPage
{
KendoGrid Grid { get; }
IWebElement PagerInfoLabel { get; set; }
IWebElement GoToNextPage { get; set; }
IWebElement GoToFirstPageButton { get; set; }
IWebElement GoToLastPage { get; set; }
IWebElement GoToPreviousPage { get; set; }
IWebElement NextMorePages { get; set; }
IWebElement PreviousMorePages { get; set; }
IWebElement PageOnFirstPositionButton { get; set; }
IWebElement PageOnSecondPositionButton { get; set; }
IWebElement PageOnTenthPositionButton { get; set; }
void NavigateTo();
}
All pages that contain grid control should be implemented. This way, your columns asserters can be used against the different pages and their grids.
This is how our first grid's page looks like:
public class GridFilterPage : IGridPage
{
public readonly string Url = @"http://demos.telerik.com/kendo-ui/grid/filter-row";
private readonly IWebDriver driver;
public GridFilterPage(IWebDriver driver)
{
this.driver = driver;
PageFactory.InitElements(driver, this);
}
public KendoGrid Grid
{
get
{
return new KendoGrid(this.driver, this.driver.FindElement(By.Id("grid")));
}
}
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/span")]
public IWebElement PagerInfoLabel { get; set; }
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/a[3]")]
public IWebElement GoToNextPage { get; set; }
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/a[1]")]
public IWebElement GoToFirstPageButton { get; set; }
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/a[4]/span")]
public IWebElement GoToLastPage { get; set; }
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/a[2]/span")]
public IWebElement GoToPreviousPage { get; set; }
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/ul/li[12]/a")]
public IWebElement NextMorePages { get; set; }
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/ul/li[2]/a")]
public IWebElement PreviousMorePages { get; set; }
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/ul/li[2]/a")]
public IWebElement PageOnFirstPositionButton { get; set; }
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/ul/li[3]/a")]
public IWebElement PageOnSecondPositionButton { get; set; }
[FindsBy(How = How.XPath, Using = "//*[@id='grid']/div[3]/ul/li[11]/a")]
public IWebElement PageOnTenthPositionButton { get; set; }
public void NavigateTo()
{
this.driver.Navigate().GoToUrl(this.Url);
}
}
Reuse Grid Control's Helper Methods
As you can recall from the previous articles from the series, we needed different helper methods like WaitForPageToLoad
, Until
, GetAllItemsFromDb
, etc. They were placed inside the test class as private
methods. However, if we have different asserters and move the whole logic in them, we will need these methods for the different test cases. By cause of that, we can move them to a new base asserter class that all other columns' asserters are going to derive from.
public class GridColumnAsserter
{
public GridColumnAsserter(IGridPage gridPage)
{
this.GridPage = gridPage;
}
protected IGridPage GridPage { get; set; }
protected void WaitForPageToLoad(int expectedPage, KendoGrid grid)
{
this.Until(() =>
{
int currentPage = grid.GetCurrentPageNumber();
return currentPage == expectedPage;
});
}
protected void WaitForGridToLoad(int expectedCount, KendoGrid grid)
{
this.Until(
() =>
{
var items = grid.GetItems<GridItem>();
return expectedCount == items.Count;
});
}
protected void WaitForGridToLoadAtLeast(int expectedCount, KendoGrid grid)
{
this.Until(
() =>
{
var items = grid.GetItems<GridItem>();
return items.Count >= expectedCount;
});
}
protected void Until(
Func<bool> condition,
int timeout = 10,
string exceptionMessage = "Timeout exceeded.",
int retryRateDelay = 50)
{
DateTime start = DateTime.Now;
while (!condition())
{
DateTime now = DateTime.Now;
double totalSeconds = (now - start).TotalSeconds;
if (totalSeconds >= timeout)
{
throw new TimeoutException(exceptionMessage);
}
Thread.Sleep(retryRateDelay);
}
}
protected List<Order> GetAllItemsFromDb()
{
List<Order> orders = new List<Order>();
for (int i = 0; i < 10; i++)
{
orders.Add(new Order());
}
return orders;
}
protected Order CreateNewItemInDb(string shipName = null)
{
return new Order(shipName);
}
protected void UpdateItemInDb(Order order)
{
}
protected int GetUniqueNumberValue()
{
var currentTime = DateTime.Now;
int result = currentTime.Year +
currentTime.Month +
currentTime.Hour +
currentTime.Minute +
currentTime.Second +
currentTime.Millisecond;
return result;
}
}
Concrete Grid Column Asserter's Implementation
The next part of the refactoring is to create the different column asserters. Below, you can find the concrete asserter for the OrderId
column. It derives from the GridColumnAsserter
class.
public class OrderIdColumnAsserter : GridColumnAsserter
{
public OrderIdColumnAsserter(IGridPage gridPage) : base(gridPage)
{
}
public void OrderIdEqualToFilter()
{
this.GridPage.NavigateTo();
var newItem = this.CreateNewItemInDb();
this.GridPage.Grid.Filter(
GridColumns.OrderID,
Enums.FilterOperator.EqualTo,
newItem.OrderId.ToString());
this.WaitForGridToLoad(1, this.GridPage.Grid);
var items = this.GridPage.Grid.GetItems<GridItem>();
Assert.AreEqual(1, items.Count);
}
public void OrderIdGreaterThanOrEqualToFilter()
{
this.GridPage.NavigateTo();
var newItem = this.CreateNewItemInDb();
var secondNewItem = this.CreateNewItemInDb(newItem.ShipName);
this.GridPage.Grid.Filter(
new GridFilter(
GridColumns.OrderID,
Enums.FilterOperator.GreaterThanOrEqualTo,
newItem.OrderId.ToString()),
new GridFilter(
GridColumns.ShipName,
Enums.FilterOperator.EqualTo,
newItem.ShipName));
this.WaitForGridToLoadAtLeast(2, this.GridPage.Grid);
var results = this.GridPage.Grid.GetItems<Order>();
Assert.AreEqual(
secondNewItem.OrderId,
results.FirstOrDefault(x => x.ShipName == secondNewItem.ShipName).OrderId);
Assert.AreEqual(
newItem.OrderId,
results.FirstOrDefault(x => x.ShipName == newItem.ShipName).OrderId);
Assert.IsTrue(results.Count() == 2);
}
public void OrderIdGreaterThanFilter()
{
this.GridPage.NavigateTo();
var newItem = this.CreateNewItemInDb();
var secondNewItem = this.CreateNewItemInDb(newItem.ShipName);
this.GridPage.Grid.Filter(
new GridFilter(
GridColumns.OrderID,
Enums.FilterOperator.GreaterThan,
newItem.OrderId.ToString()),
new GridFilter(
GridColumns.ShipName,
Enums.FilterOperator.EqualTo,
newItem.ShipName));
this.WaitForGridToLoadAtLeast(1, this.GridPage.Grid);
var results = this.GridPage.Grid.GetItems<Order>();
Assert.AreEqual(
secondNewItem.OrderId,
results.FirstOrDefault(x => x.ShipName == secondNewItem.ShipName).OrderId);
Assert.IsTrue(results.Count() == 1);
}
public void OrderIdLessThanOrEqualToFilter()
{
this.GridPage.NavigateTo();
var newItem = this.CreateNewItemInDb();
var secondNewItem = this.CreateNewItemInDb(newItem.ShipName);
this.GridPage.Grid.Filter(
new GridFilter(
GridColumns.OrderID,
Enums.FilterOperator.LessThanOrEqualTo,
secondNewItem.OrderId.ToString()),
new GridFilter(
GridColumns.ShipName,
Enums.FilterOperator.EqualTo,
newItem.ShipName));
this.WaitForGridToLoadAtLeast(2, this.GridPage.Grid);
var results = this.GridPage.Grid.GetItems<Order>();
Assert.AreEqual(
newItem.OrderId,
results.FirstOrDefault(x => x.ShipName == newItem.ShipName).OrderId);
Assert.AreEqual(
secondNewItem.OrderId,
results.Last(x => x.ShipName == secondNewItem.ShipName).OrderId);
Assert.IsTrue(results.Count() == 2);
}
public void OrderIdLessThanFilter()
{
this.GridPage.NavigateTo();
var newItem = this.CreateNewItemInDb();
var secondNewItem = this.CreateNewItemInDb(newItem.ShipName);
this.GridPage.Grid.Filter(
new GridFilter(
GridColumns.OrderID,
Enums.FilterOperator.LessThan,
secondNewItem.OrderId.ToString()),
new GridFilter(
GridColumns.ShipName,
Enums.FilterOperator.EqualTo,
secondNewItem.ShipName));
this.WaitForGridToLoadAtLeast(1, this.GridPage.Grid);
var results = this.GridPage.Grid.GetItems<Order>();
Assert.AreEqual(
newItem.OrderId,
results.FirstOrDefault(x => x.ShipName == newItem.ShipName).OrderId);
Assert.IsTrue(results.Count() == 1);
}
public void OrderIdNotEqualToFilter()
{
this.GridPage.NavigateTo();
var newItem = this.CreateNewItemInDb();
var secondNewItem = this.CreateNewItemInDb(newItem.ShipName);
this.GridPage.Grid.Filter(
new GridFilter(
GridColumns.OrderID,
Enums.FilterOperator.NotEqualTo,
secondNewItem.OrderId.ToString()),
new GridFilter(
GridColumns.ShipName,
Enums.FilterOperator.EqualTo,
secondNewItem.ShipName));
this.WaitForGridToLoadAtLeast(1, this.GridPage.Grid);
var results = this.GridPage.Grid.GetItems<Order>();
Assert.AreEqual(
newItem.OrderId,
results.FirstOrDefault(x => x.ShipName == newItem.ShipName).OrderId);
Assert.IsTrue(results.Count() == 1);
}
public void OrderIdClearFilter()
{
this.GridPage.NavigateTo();
var newItem = this.CreateNewItemInDb();
this.CreateNewItemInDb(newItem.ShipName);
this.GridPage.Grid.Filter(
GridColumns.OrderID,
Enums.FilterOperator.EqualTo,
newItem.OrderId.ToString());
this.WaitForGridToLoad(1, this.GridPage.Grid);
this.GridPage.Grid.RemoveFilters();
this.WaitForGridToLoadAtLeast(1, this.GridPage.Grid);
var results = this.GridPage.Grid.GetItems<Order>();
Assert.IsTrue(results.Count() > 1);
}
}
If you recall the code of the examples from the previous articles, you will notice that the above code is almost identical. The main differences are that the helper methods are placed inside the base class and that the different methods are not marked as MSTest
test methods. Also, the grid control is accessed through the IGridPage
interface so that we can reuse the test cases for different grids. The asserters for the rest of the columns are similar to this example so I am not going to publish their code. If you are interested, you can download the fully working examples at the end of the article.
Grid Column Asserters in Tests
Grid Control's Tests Setup
This is how looks the setup of the refactored version of the grid control's tests.
[TestClass]
public class KendoGridAdvanceReuseTacticsAutomationTests
{
private IWebDriver driver;
private IGridPage gridPage;
private FreightColumnAsserter freightColumnAsserter;
private OrderDateColumnAsserter orderDateColumnAsserter;
private OrderIdColumnAsserter orderIdColumnAsserter;
private ShipNameColumnAsserter shipNameColumnAsserter;
private GridPagerAsserter gridPagerAsserter;
[TestInitialize]
public void SetupTest()
{
this.driver = new FirefoxDriver();
this.driver.Manage().Timeouts().SetPageLoadTimeout(TimeSpan.FromSeconds(5));
this.gridPage = new GridFilterPage(this.driver);
this.freightColumnAsserter = new FreightColumnAsserter(this.gridPage);
this.orderDateColumnAsserter = new OrderDateColumnAsserter(this.gridPage);
this.orderIdColumnAsserter = new OrderIdColumnAsserter(this.gridPage);
this.shipNameColumnAsserter = new ShipNameColumnAsserter(this.gridPage);
this.gridPagerAsserter = new GridPagerAsserter(this.gridPage);
}
[TestCleanup]
public void TeardownTest()
{
this.driver.Quit();
}
You assign the concrete implementation of the concrete grid's page to the IGridPage
interface variable. Then you initialize only the required grid columns' asserters. This means that if for example the ship name column is not present on this grid, you won't include its asserter. You can have different combinations of column asserters depending on the columns' combination in the currently tested grid.
Grid Control's Tests
#region OrderID Test Cases
[TestMethod]
public void OrderIdEqualToFilter()
{
this.orderIdColumnAsserter.OrderIdEqualToFilter();
}
[TestMethod]
public void OrderIdGreaterThanOrEqualToFilter()
{
this.orderIdColumnAsserter.OrderIdGreaterThanOrEqualToFilter();
}
[TestMethod]
public void OrderIdGreaterThanFilter()
{
this.orderIdColumnAsserter.OrderIdGreaterThanFilter();
}
[TestMethod]
public void OrderIdLessThanOrEqualToFilter()
{
this.orderIdColumnAsserter.OrderIdLessThanOrEqualToFilter();
}
[TestMethod]
public void OrderIdLessThanFilter()
{
this.orderIdColumnAsserter.OrderIdLessThanFilter();
}
[TestMethod]
public void OrderIdNotEqualToFilter()
{
this.orderIdColumnAsserter.OrderIdNotEqualToFilter();
}
[TestMethod]
public void OrderIdClearFilter()
{
this.orderIdColumnAsserter.OrderIdClearFilter();
}
#endregion
As you can see, the usage of the column asserter is pretty simple. An additional benefit is the readability of the tests. Moreover, if you need to fix some of the test cases or refactor them, you can do it only in a single place, directly in the column asserter. This makes your code even more maintainable. You can find the rest of the examples in the full source code.
If you enjoy my publications, Get Instant Access to my future PRO tips and trips.
So Far in the 'Pragmatic Automation with WebDriver' Series
The post Advanced Reuse Tactics for Grid Controls Automated Tests appeared first on Automate The Planet.
All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement