Wednesday, December 16, 2009

Design of Selenium tests for ASP.NET: Autogenerate Page Objects for Selenium tests with T4 templates in .NET. Part 2

In previous post it was described how to generate Page Objects for Selenium test on example with simple page. But what to do with more complex cases, when there are a lot of elements with identical ids or it is not possible to include information about type in id of control?

Lets consider page to manage users from design-of-selenium-tests-for-asp-net sample project:

image_thumb3

It contains a form to insert users in system and also list of already inserted users. The main problem is to get values from table – they are not wrapped in any container and therefore do not have any ids. It is only possible to access them by XPath:

Take a look at tr elements, which are rounded by red square. It is only needed to change number next to “td” element and we will get XPath for each element in table.

So, to generate code to access table we need:

1) Xpathes to all elements in one grid:

/html/body/form[@id='form1']/div[3]/div[@id='holderUsers']/table/tbody/tr[1]/td[1]
/html/body/form[@id='form1']/div[3]/div[@id='holderUsers']/table/tbody/tr[1]/td[2]

2) Pattern to identify place where to change row number:

table/tbody/tr[{0}]/td

3) Xpath to identify count of items in grid:

/html/body/form[@id='form1']/div[3]/div[@id='holderUsers']/table/tbody/tr

4) Give meaningful names for each element in row:

/html/body/form[@id='form1']/div[3]/div[@id='holderUsers']/table/tbody/tr[1]/td[1] {Name=lblUser}
/html/body/form[@id='form1']/div[3]/div[@id='holderUsers']/table/tbody/tr[1]/td[2] {Name=lblPassword}

Now id is needed to give this information to generator:

<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" language="C#v3.5" debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ include file="PageObjectGeneratorByXPath.ttinclude" #>
<#
string url = "Home.aspx";

var mainClass = new ClassInfo {
ClassName = "Home",
Elements =
@"/html/body/form[@id='form1']/div[3]/span[@id='lblUserName']
/html/body/form[@id='form1']/div[3]/input[@id='btnLogout']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[3]/td[2]/input[@id='txtName']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[4]/td[2]/input[@id='txtPassword']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[5]/td/input[@id='btnAddUser']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[2]/td/span[@id='lblError']"};
var rowClasses = new List<ClassRowInfo>();

rowClasses.Add(
new ClassRowInfo {
ClassName = "UserTable",
CountXpath = "/html/body/form[@id='form1']/div[3]/div[@id='holderUsers']/table/tbody/tr",
IteratorPattern = "tbody/tr[{0}]/td",
Elements =
@"/html/body/form[@id='form1']/div[3]/div[@id='holderUsers']/table/tbody/tr[1]/td[1] {Name=lblUser}
/html/body/form[@id='form1']/div[3]/div[@id='holderUsers']/table/tbody/tr[1]/td[2] {Name=lblPassword}"
});


#>

<#
RenderClasses( mainClass, rowClasses, url);
#>

And from this point we can access values in rows using generated Page Object in the following way:

public HomePageFlow AssertThatUserListContains(string name, string password)
{
var isExists = false;

for (var i = 1; i <= _home.GetUserTableRowsCount(); i++)
{
var row = _home.GetUserTableRow(i);

if(row.User.GetText() == name && row.Password.GetText() == password)
{
isExists = true;
}
}

Assert.That(isExists, Is.EqualTo(true), string.Format("User with name '{0}' and password '{1}' doesn't exists in user list table", name, password));

return this;
}

All examples from article can be found in the design-of-selenium-tests-for-asp-net sample project.

0 comments: