Wednesday, December 9, 2009

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

In my previous post Design of Selenium tests for ASP.NET: Page Object and Navigator patterns i described Page Object pattern which helps to improve maintainability of tests through UI. But to maintain these Page Object is still great effort… And good news, everybody! We can easily autogenerate them! How?

Firstly we need a list of all important controls on page. Usually they are inputs and links, but also spans, headers, lists… whatever. Plug-in Xpather for Firefox can easily find them all). Just type “//input | //span” in “XPath” textbox, select needed controls and copy list of XPaths from “XPaths” tab:

Xpather

You will get list like this:

/html/body/form[@id='form1']/div[3]/table/tbody/tr[1]/td/span[@id='lblMessage']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[2]/td[2]/input[@id='txtUser']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[3]/td[2]/input[@id='txtPassword']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[4]/td/input[@id='btnLogin']

Look at these XPathes – it is possible to extract all information that needed to identify type and name of each control. Also, as you can see i added prefixes to control names in order to recognize whether it is button or textbox. If yours style guidelines are prohibit such names, you can find another workaround – for example add some attributes manually after gaining them from XPather.

The best way to autogenerate code in Visual Studio is T4 templates. It is needed to create simple transformation and insert our Xpathes in it:

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

var mainClass = new ClassInfo {
ClassName = "Login",
Elements =
@"/html/body/form[@id='form1']/div[3]/table/tbody/tr[1]/td/span[@id='lblMessage']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[2]/td[2]/input[@id='txtUser']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[3]/td[2]/input[@id='txtPassword']
/html/body/form[@id='form1']/div[3]/table/tbody/tr[4]/td/input[@id='btnLogin']"};
var rowClasses = new List<ClassRowInfo>();
#>

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


Implementation of RenderClasses function can be found in design-of-selenium-tests-for-asp-net sample solution in LoginPage Page Object template.

This template will autogenerate next ready to use code:

using Tests.SmokeTest.Core;
using Tests.SmokeTest.PageObjects.Controls;
using Selenium;

namespace Tests.SmokeTest.PageObjects
{
public partial class LoginPage : PageBase
{
public LoginPage() : base("Login.aspx") { }

private Label _message = null;
public Label Message
{
get
{
if (_message == null)
{
_message = new Label(Selenium, @"xpath=/html/body/form[@id='form1']/div[3]/table/tbody/tr[1]/td/span[@id='lblMessage']");
}
return _message;
}
}
private TextField _user = null;
public TextField User
{
get
{
if (_user == null)
{
_user = new TextField(Selenium, @"xpath=/html/body/form[@id='form1']/div[3]/table/tbody/tr[2]/td[2]/input[@id='txtUser']");
}
return _user;
}
}
private TextField _password = null;
public TextField Password
{
get
{
if (_password == null)
{
_password = new TextField(Selenium, @"xpath=/html/body/form[@id='form1']/div[3]/table/tbody/tr[3]/td[2]/input[@id='txtPassword']");
}
return _password;
}
}
public void ClickLogin()
{
Selenium.Click(@"xpath=/html/body/form[@id='form1']/div[3]/table/tbody/tr[4]/td/input[@id='btnLogin']");
}
}
}

What benefits provides autogeneration of Page Objects? first of all it is not longer needed to painstakingly extract these ids from html and hardcode them into code. Secondly, if page is changed, then autogenerated Page Object will contain another set of properties and its types, and this will cause errors in compile time. This errors are pretty easy to fix unlike to runtime errors with hardcoded IDs or Xpathes.

To get know how code generation works for pages with grids and for controls without id please refer part 2 article.

Next post will describe how to improve readability of tests using Flow pattern.


kick it on DotNetKicks.com

0 comments: