I’ll be posting potential interview questions that I come up with from time to time on my blog – so that I can refer to it later and also to share them with others. Here is one I came up with today:
Re-write the following code-snippet so that it complies with the DRY principle:
if(!_autoenroll) { if(displayEnrollPage == true) { ltlView1.Text = "Enroll Online - Page 1 of 4"; ltlView2.Text = "Enroll Online - Page 2 of 4"; ltlView3.Text = "Enroll Online - Page 3 of 4"; ltlView4.Text = "Enroll Online - Page 4 of 4"; } else { ltlView1.Text = "Enroll Online - Page 1 of 3"; ltlView2.Text = "Enroll Online - Page 2 of 3"; ltlView3.Text = "Enroll Online - Page 3 of 3"; // don't set header for the 4th page because it wont be visible. } } else { if(displayEnrollPage == true) { ltlView1.Text = "Enroll Online - Page 1 of 3"; ltlView2.Text = "Enroll Online - Page 2 of 3"; ltlView4.Text = "Enroll Online - Page 3 of 3"; } else { ltlView1.Text = "Enroll Online - Page 1 of 2"; ltlView2.Text = "Enroll Online - Page 2 of 2"; // don't set header for 4th page because it wont be visible. } }
No, I won’t be posting the answers. But feel free to suggest one inside the comments section!
Rendering Icons as links in ASP .NET MVC
Modifying the behaviors of HTML helper methods such as ActionLink is a pain and requires quite a bit of code. Sometimes, however, you get lucky and are able to avoid it. What follows is once such case.
Here is one quick way that you can use to render an icon as a link in ASP .NET MVC.
First, create a CSS style for the icon:
.print-icon { background: url("/assets/print-icon.png"); }
Next, use it when generating the link:
@Html.ActionLink(" ", "Action", new { id = /* blah */ }, new { @class = "print-icon" }
And, that does it!
Cheers!
Writing Integration Tests for ASP .NET with Selenium 2.0 – Part 2
This is the second in a series of posts on writing integration tests for ASP .NET using the Selenium 2.0 web application testing system.
In this post, I’ll go over how to write and run C# test-cases using Selenium 2.0. I’ve also provided a base-class that contains helper methods for repetitive stuff like typing inside input fields. The base-class also speeds up the tests by re-using the same driver and preserving logins across tests.
Getting ready to write your first test-case…
Make sure your test project references the following assemblies:
- WebDriver.dll
- WebDriver.Support.dll
- Newtonsoft.Json.dll
- Ionic.Zip.dll
- Castle.Core.dll
Refer back to my first post on Selenium to learn about how to configure Selenium and where to grab these DLLs from.
Your first test-case
For the purposes of writing our test case, we will assume that we have a web application that allows a user to login, fill out a form and submit it. Below are basic HTML elements that we care about:
Login.aspx
<input type="text" id="userId" /> <input type="password" id="password" /> <input type="submit" id="btnLogin" value="Log In" />
FillForm.aspx
<input type="text" id="firstName" /> <input type="text" id="lastName" /> <input type="text" id="address1" /> <input type="text" id="city" /> <input type="text" id="state" /> <input type="submit" name="btnSubmit" value="Submit" />
Here is what our test-class would look like:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium; using OpenQA.Selenium.Firefox; namespace Web.UI.Tests { [TestClass] public class FillFormIntegrationTest { public static string BaseUrl = "http://localhost:8080"; // the max. time to wait before timing out. public const int TimeOut = 30; [TestMethod] public void CanFillAndSubmitFormAfterLogin() { var driver = new FirefoxDriver(); // we need to setup implicit wait times so that selenium waits for some time and // re-checks if an element isn't found. // This is useful to ensure that a page gets fully loaded before selenium tries to look for stuff. driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(TimeOut)); driver.Navigate().GoToUrl(BaseUrl + "/Login.aspx"); driver.FindElement(By.Id("userId")).SendKeys("testUser"); driver.FindElement(By.Id("password")).SendKeys("foobar"); driver.FindElement(By.Id("btnLogin")).Click(); Assert.AreEqual("Fill out form", driver.Title); driver.FindElement(By.Id("firstName")).SendKeys("User"); driver.FindElement(By.Id("lastName")).SendKeys("One"); driver.FindElement(By.Id("address1")).SendKeys("99 Test Street"); driver.FindElement(By.Id("city")).SendKeys("Test City"); driver.FindElement(By.Id("state")).SendKeys("TX"); driver.FindElement(By.Id("btnSubmit")).Click(); Assert.IsTrue(driver.FindElement(By.CssSelector("confirm-label")).Text. Contains("Submission successful.")); } } }
And there it is! If you run this test case, you’ll see the Firefox browser come up and go through actions specified in the test case above.
Saving our fingers and speeding up our tests!
If you happen to find the Selenium API a little verbose then you’re not alone. I feel the same way. Having to type ‘driver.FindElement(By.Name(“firstName”))’ for every single action starts getting tedious pretty quick – even with IntelliSense! The other issue that you’ll notice as soon as you have more than one test-case is that starting up the FirefoxDriver takes a bit of time. What we really want is a way to create the driver once and then simply re-use it across all our tests. Another pain point are logins. For most apps, a great majority of tests will require that a user be logged in. Furthermore, you’re likely re-use the same login across most of your test-cases. Instead of having each of our tests log the user in every single time, why not skip the login process if the user is already logged in? Both these changes will make a significant impact in speeding up our tests. So let’s add them!
Below is a base-level class that contains the improvements pointed out above and also provides some helper methods to give our fingers a break!
public class BaseIntegrationTest
{
public const string BaseUrl = "http://localhost:8888";
// specify if our web-app uses a virtual path
private const string VirtualPath = "";
private const int TimeOut = 30;
private static int _testClassesRunning;
private static readonly IWebDriver StaticDriver = CreateDriverInstance();
private static Login _currentlyLoggedInAs;
static BaseIntegrationTest()
{
StaticDriver.Manage().Timeouts().ImplicitlyWait(
TimeSpan.FromSeconds(TimeOut));
}
// Pass in null if want to run your test-case without logging in.
public static void ClassInitialize(Login login = null)
{
_testClassesRunning++;
if (login == null)
{
Logoff();
}
else if (!IsCurrentlyLoggedInAs(login))
{
Logon(login);
}
}
public static void ClassCleanup()
{
try
{
_testClassesRunning--;
if (_testClassesRunning == 0)
{
StaticDriver.Quit();
}
}
catch (Exception)
{
// Ignore errors if unable to close the browser
}
}
public IWebDriver Driver
{
get { return StaticDriver; }
}
public void Open(string url)
{
Driver.Navigate().GoToUrl(BaseUrl + VirtualPath + url.Trim('~'));
}
public void Click(string id)
{
Click(By.Id(id));
}
public void Click(By locator)
{
Driver.FindElement(locator).Click();
}
public void ClickAndWait(string id, string newUrl)
{
ClickAndWait(By.Id(id), newUrl);
}
/// <summary>
/// Use when you are navigating via a hyper-link and need for the page to fully load before
/// moving further.
/// </summary>
public void ClickAndWait(By locator, string newUrl)
{
Driver.FindElement(locator).Click();
WebDriverWait wait = new WebDriverWait(Driver,
TimeSpan.FromSeconds(TimeOut));
wait.Until(d => d.Url.Contains(newUrl.Trim('~')));
}
public void AssertCurrentPage(string pageUrl)
{
var absoluteUrl = new Uri(new Uri(BaseUrl), VirtualPath +
pageUrl.Trim('~')).ToString();
Assert.AreEqual(absoluteUrl, Driver.Url);
}
public void AssertTextContains(string id, string text)
{
AssertTextContains(By.Id(id), text);
}
public void AssertTextContains(By locator, string text)
{
Assert.IsTrue(Driver.FindElement(locator).Text.Contains(text));
}
public void AssertTextEquals(string id, string text)
{
AssertTextEquals(By.Id(id), text);
}
public void AssertTextEquals(By locator, string text)
{
Assert.AreEqual(text, Driver.FindElement(locator).Text);
}
public void AssertValueContains(string id, string text)
{
AssertValueContains(By.Id(id), text);
}
public void AssertValueContains(By locator, string text)
{
Assert.IsTrue(GetValue(locator).Contains(text));
}
public void AssertValueEquals(string id, string text)
{
AssertValueEquals(By.Id(id), text);
}
public void AssertValueEquals(By locator, string text)
{
Assert.AreEqual(text, GetValue(locator));
}
public IWebElement GetElement(string id)
{
return Driver.FindElement(By.Id(id));
}
public string GetValue(By locator)
{
return Driver.FindElement(locator).GetAttribute("value");
}
public string GetText(By locator)
{
return Driver.FindElement(locator).Text;
}
public void Type(string id, string text)
{
var element = GetElement(id);
element.Clear();
element.SendKeys(text);
}
public void Uncheck(string id)
{
Uncheck(By.Id(id));
}
public void Uncheck(By locator)
{
var element = Driver.FindElement(locator);
if(element.Selected)
element.Click();
}
// Selects an element from a drop-down list.
public void Select(string id, string valueToBeSelected)
{
var options = GetElement(id).FindElements(By.TagName("option"));
foreach (var option in options)
{
if(valueToBeSelected == option.Text)
option.Click();
}
}
private static IWebDriver CreateDriverInstance(string baseUrl = BaseUrl)
{
return new FirefoxDriver();
}
private static bool IsCurrentlyLoggedInAs(Login login)
{
return _currentlyLoggedInAs != null &&
_currentlyLoggedInAs.Equals(login);
}
private static void Logon(Login login)
{
StaticDriver.Navigate().GoToUrl(BaseUrl + VirtualPath + "/Logon.aspx");
StaticDriver.FindElement(By.Id("userId")).SendKeys(login.Username);
StaticDriver.FindElement(By.Id("password")).SendKeys(login.Password);
StaticDriver.FindElement(By.Id("btnLogin")).Click();
_currentlyLoggedInAs = login;
}
private static void Logoff()
{
StaticDriver.Navigate().GoToUrl(
VirtualPath + RedirectLinks.SignOff.Trim('~'));
_currentlyLoggedInAs = null;
}
}
public class Login
{
public Login(string username, string password)
{
Username = username;
Password = password;
}
public string Username { get; private set; }
public string Password { get; private set; }
public override bool Equals(object obj)
{
Login compareTo = obj as Login;
if (compareTo == null)
return false;
return compareTo.Username == Username &&
compareTo.Password == Password;
}
}
public class Logins
{
public static Login UserOne
{
get
{
return new Login("user_1", "foobar1");
}
}
public static Login UserTwo
{
get
{
return new Login("user_2", "foobar2");
}
}
}Upon inheriting from BaseIntegrationTest class, our test-case will look like the following:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium; using OpenQA.Selenium.Firefox; namespace Web.UI.Tests { [TestClass] public class FillFormIntegrationTest : BaseIntegrationTest { [ClassInitialize] public static void ClassInitialize(TestContext context) { ClassInitialize(Logins.UserOne); } [ClassCleanup] public static void ClassCleanup() { BaseIntegrationTest.ClassCleanup(); } [TestMethod] public void CanFillAndSubmitFormAfterLogin() { Open("~/FillOutForm.aspx"); Assert.AreEqual("Fill out form", driver.Title); Type("firstName", "User"); Type("lastName", "One"); Type("address1", "99 Test Street"); Type("city", "Test City"); Type("state", "TX"); Click("btnSubmit"); AssertTextContains(By.CssSelector("confirm-label"), "Submission successful."); } } }
Much better, don’t you think?
In my next and final post on Selenium, I’ll go over the use of cookies to simulate and test various scenarios when integrating with 3rd party software.
Cheers!
Integrating TypeMock with ASP .NET Unit tests
When it comes to writing unit tests for your ASP .NET pages, there isn’t much help out there. I experimented with a few open source testing tools and found some major limitations.
Both NUnitAsp and WaitN, for instance, are “client-side” tools. In other words, you have to write your tests against the actual HTML output. For example, to get the value of a textbox, you have to specify the actual HTML id of the textbox. That’s painful! Especially, since ASP .NET ends up assigning long and complicated ID’s to your controls. Plus NUnitAsp is no longer being maintained or supported.
Unlike NUnitAsp and WaitN, VS Studio ASP .NET Unit testing let’s you examine the actual HttpRequest object. What this means is that you can call methods on your Page and get access to the controls within the page. VS Studio ASP .NET is a pretty decent tool and maybe the answer for you. IF you don’t need to use TypeMock that is. But if you do, then tough luck because VS Studio ASP .NET unit tests don’t work with TypeMock. If you give it try, you’ll get the following exception:
Test method AzAsh.WebApp.Tests.DefaultTest.LoginNotRequiredTest threw exception: TypeMock.TypeMockException:
*** Typemock Isolator is not currently enabled.
To enable do one of the following:
* To run Typemock Isolator as part of an automated process you can:
- run tests via TMockRunner.exe command line tool
- use 'TypeMockStart' tasks for MSBuild or NAnt
* To work with Typemock Isolator inside Visual Studio.NET:
set Tools->Enable Typemock Isolator from within Visual Studio
For more information consult the documentation (see 'Running' topic).
Check the enable property as they have suggested and you’ll notice that Typemock is enabled! So, what gives? I have no idea. But I do know that Ivonna – a ASP .NET testing tool that is being developed in partnership with TypeMock WILL let you work in conjunction with TypeMock. Like VS Studio, it allows you to examine the intrinsic objects, such as the Page object. In addition, it’s got another neat feature that let’s you inject setup code and assertions into your page’s lifecycle event handlers – very handy especially during type mocking. The only drawback is that it’s a little slow. The unit tests take a while to run.
So if you’ve been scratching your head trying to figure how to develop ASP .NET tests that can work with TypeMock, Ivonna is probably the tool you’ve been waiting for!
Update Panels dont play well with Validators in Chrome
If your page uses ASP .NET custom validators and an UpdatePanel, you will notice that your UpdatePanels will not work in Chrome. In order to fix this, you will have disable client script on the custom validators by setting EnableClientScript=false.
Hopefully this tip will save others the headache that I went through!
Happy Coding!
Making ASP .NET Gridview a little less painful
Isn’t it pain in the neck when you have to write the following for every input control whose value you want to extract from a Gridview:
public static void myGridView_RowUpdating(object sender, GridViewUpdateEventArgs e) { string firstname = ((TextBox)myGridview.Rows[e.RowIndex]. FindControl("txtFirstname")).Text; string lastname = ((TextBox)myGridview.Rows[e.RowIndex]. FindControl("txtLastname")).Text; //....and so on.. }
Well, you can make it a little easier by implementing the following method either in a class that extends GridView or by writing a extension method. In this example, I’ve written it as an extension method.
public static T FindControl<T>(this GridView gridView, string cntrlName, int rowIndex) where T : Control { return (T)(gridView.Rows[rowIndex].FindControl(cntrlName)); }
Now the code inside my page is little cleaner and easier to write:
public static void myGridView_RowUpdating(object sender, GridViewUpdateEventArgs e) { string firstname = gridView.FindControl<TextBox>("txtFirstname", e.RowIndex).Text; string lastname = gridView.FindControl<TextBox>("txtLastname", e.RowIndex).Text; //....and so on.. }
I admit it’s not something huge but it definitely is a big help when you have to type in that statement for every single control in your Gridview!
I hope that helps some!
An easier way to write HTML input forms
Consider the block of code below:
<table cellpadding="3"> <tr> <td valign="top"> <b>First Name</b> </td> <td> <asp:TextBox ID="txtFirstName" runat="server" /> <div> Please enter your first name.</div> </td> </tr> <tr> <td valign="top"> <b>Last name</b> </td> <td> <asp:TextBox ID="txtLastName" runat="server" /> <div> Please enter your last name.</div> </td> </tr> </table>
The rendered output looks something like this: By implementing a couple of simple ASP .NET server controls, we will save ourselves the trouble of typing the above shown HTML and will be able to simply type the following: Now isn't that soo much more pleasant to the eyes and easier on your fingers? So how is this accomplished? As follows: First, create a ASP .NET Server Control name Form.cs. This simple control will simply render the Notice the use of Now we move on to the more interesting control: the FormField.cs control. This control renders the The interesting parts of the class are the And the Well, that all sounds well and good. But wait! Where is the FormField control rendering the actual input field (the asp:TextBox)? The answer is that it isn’t. The use of the Notice that the TextBox (txtFirstName) is a child of the Page instead of being a child of the FormField control. What this means is that instead of having to type something convoluted like: your page can directly reference the TextBox like so: I hope you’ve found this post helpful and informative. Please take some time to leave your feedback and/or comments to help me improve.
You will notice that we have to repeat the
and the
tags, along with any style information that these contain, for every row of our input form. Ouch! That's a lot of typing. Furthermore, if, in the future, we have make layout and/or style related changes, we'd have to modify all the pages in our application that contain these HTML input forms! Fortunately, there is an easier way.
<cc:Form runat="server">
<cc:FormField runat="server" Name="First Name"
Description="Please enter the first name.">
<asp:TextBox ID="txtFirstName" runat="server" />
</cc:FormField>
<cc:FormField runat="server" Name="Last Name"
Description="Please enter the last name.">
<asp:TextBox ID="txtLastName" runat="server" />
</cc:FormField>
</cc:Form>
tags. Below is the code for it:
public class Form : WebControl
{
public override void RenderBeginTag(HtmlTextWriter writer)
{
writer.Write("<table cellpadding='3'>");
}
public override void RenderEndTag(HtmlTextWriter writer)
{
writer.Write("</table>");
}
}
[ParseChildren(false)] attribute. This attributes ensures that 1) we will be able to nest additional controls inside this control and 2) The nested control won't actually become a child control this control. The second point is important and will explained in greater detail shortly.
's,
's, the input field label, and the input field description. Below is the code for it:
[ToolboxData("<{0}:FormField runat=server></{0}:FormField>"), ParseChildren(false)]
public class FormField : WebControl
{
public string Name
{
get { return ViewState.GetAsString("Name"); }
set { ViewState["Name"] = value; }
}
public string Description
{
get { return ViewState.GetAsString("Description"); }
set { ViewState["Description"] = value; }
}
public string NameAlign
{
get { return ViewState.GetAsString("NameAlign"); }
set { ViewState["NameAlign"] = value; }
}
public string ControlAlign
{
get { return ViewState.GetAsString("ControlAlign"); }
set { ViewState["ControlAlign"] = value; }
}
public override void RenderBeginTag(HtmlTextWriter writer)
{
string beginHtml = @"<tr><td align={0}><b>{1}</b></td><td align={2}>";
writer.Write(string.Format(beginHtml, NameAlign, Name, ControlAlign));
}
public override void RenderEndTag(HtmlTextWriter writer)
{
string endHtml = @"<div>{0}</div></td></tr>";
writer.Write(endHtml, Description);
}
}
RenderBeginTag() and the RenderEndTag() methods. If you inspect these methods, you’ll see that the RenderBeginTag() is responsible for rendering:
tag
tag
<td><b>First Name</b></td>
tag for the column that contains the actual input field. tag for the column that contains the actual input field (like the
, for example)
RenderEndTag() method is responsible for rendering:
<div>Please enter the first name</div>
[ParseChildren(false)] attribute prevents the parsing of any embedded controls that FormField may have. Thus, in our example, when the Form and the FormField control's rendering has finished, the page control tree looks something like this:
- Page
- Form
- FormField
- LiteralControl("")
- LiteralControl("
")
- TextBox string firstName = ((TextBox)this.formField1.FindControl("txtFirstName")).Text;
string firstName = txtFirstname.Text

