Creating Your Own Custom HtmlControls

Let's suppose you're testing a web site that uses a third party or custom control. You can easily create your own custom HtmlControl class and then design tests that interact with the custom control via your custom HtmlControl class.

 

As an example, let's create a custom HtmlControl that wraps the ASP.NET calendar control. We'll give it some methods for reading and controlling the date selection along with a custom ClientSideLocator just for an example. First here's our ASPX web page containing a calendar control that we want to test:

 

<% @ Page Language="C#" MasterPageFile="~/AppMaster.master" Title="Untitled Page" %>
<% @ Import Namespace="System.Data" %>
<% @ Register Assembly="ArtOfTest.WebAii.AspNet" Namespace="ArtOfTest.WebAii.AspNet.WebControls" TagPrefix="test" %>
<script runat="server">
     /// <summary>
     /// Page Load
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     protected void Page_Load(object sender, EventArgs e)
     {
           if (!Page.IsPostBack)
           {
                // Bind the grid and cache the data in the session.
                grid1.DataSource = GetData();
                DataBind();
           }
     }
     /// <summary>
     /// Return a DataSet of some random data.
     /// </summary>
     /// <returns>DataSet</returns>
     private DataSet GetData()
     {
          DataSet data = new DataSet();
          DataTable dt = new DataTable();
          Random r = new Random(3);
          data.Tables.Add(dt);
          dt.Columns.Add("ID", typeof(int));
          dt.Columns.Add("Date", typeof(DateTime));
          dt.Columns.Add("FirstName", typeof(string));
          dt.Columns.Add("LastName", typeof(string));
          dt.Columns.Add("Balance", typeof(int));
          for (int i = 0; i < 15; i++)
          {
               DataRow row = dt.NewRow();
               row.ItemArray = new object[] {i,DateTime.Today.AddDays(i),"Name" + i.ToString(),
                    "Last" + i.ToString(), r.Next(3000)};
               dt.Rows.Add(row);
          }
          return data;
     }
     /// <summary>
     /// OnCalendarSelectionChange()
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     protected void cal1_SelectionChanged(object sender, EventArgs e)
     {
          // Get DataSet from Session
          TimeSpan diffDate = cal1.SelectedDate.Subtract(DateTime.Today);
          if (cal1.SelectedDate >= DateTime.Today && diffDate.Days < 15)
          {
               grid1.SelectedIndex = diffDate.Days;
               grid1.EditIndex = diffDate.Days;
           }
           grid1.DataSource = GetData();
           grid1.DataBind();
     }
</script>
  
<asp:Content ID="Content1" ContentPlaceHolderID="PageContent" runat="Server">
     <!-- This Data Page Content -->
     <test:TestRegion ID="DataPageContent" runat='server'>
          You are logged in.
         <table style="border: dotted 1px black; background-color: beige; width:80%;" cellpadding="15">
              <tr>
              <td>
                   Select A Date:
                   <!-- The Calendar Control Region -->
                   <test:TestRegion ID="CalendarControl" runat="server">
                        <asp:Calendar ID="cal1" runat="server" OnSelectionChanged="cal1_SelectionChanged"
                             BackColor="#FFFFCC" BorderColor="#FFCC66" BorderWidth="1px" Font-Names="Verdana"
                             Font-Size="8pt" ForeColor="#663399" Height="200px" Width="220px" DayNameFormat="Shortest"
                             ShowGridLines="True">
                             <SelectedDayStyle BackColor="#CCCCFF" Font-Bold="True" />
                             <TodayDayStyle BackColor="#FFCC66" ForeColor="White" />
                             <OtherMonthDayStyle ForeColor="#CC9966" />
                             <NextPrevStyle Font-Size="9pt" ForeColor="#FFFFCC" />
                             <DayHeaderStyle Font-Bold="True" BackColor="#FFCC66" Height="1px" />
                             <TitleStyle BackColor="#990000" Font-Bold="True" Font-Size="9pt" ForeColor="#FFFFCC" />
                             <SelectorStyle BackColor="#FFCC66" />
                        </asp:Calendar>
                   </test:TestRegion>
                   <!-- The Calendar Control Region -->
              </td>
              <td valign="top" style="border: solid 1px black;">
                   Customer Balances:
                   <!-- The GridView Region -->
                   <test:TestRegion ID="GridControl" runat="server">
                        <asp:GridView ID="grid1" runat="server" AutoGenerateColumns="False" BackColor="White" 
                             BorderColor="#CC9966" BorderStyle="None" BorderWidth="1px" CellPadding="4" Font-Size="XX-Small">
                             <Columns>
                                  <asp:TemplateField HeaderText="ID">
                                       <ItemTemplate>
                                            <%# Eval("ID") %>
                                       </ItemTemplate>
                                       <EditItemTemplate>
                                            <!-- ID Column Region [Will persist only in Edit Mode] -->
                                            <test:TestRegion ID="GridEditId" runat="server">
                                                 <%# Eval("ID") %>
                                            </test:TestRegion>
                                            <!-- ID Column Region -->
                                       </EditTemplate>
                                  </asp:TemplateField>
                                  <asp:BoundField HeaderText="FirstName" DataField="FirstName" ReadOnly="true" />
                                  <asp:BoundField HeaderText="LastName" DataField="LastName" ReadOnly="true" />
                                  <asp:BoundField HeaderText="Date" DataField="Date" ReadOnly="true" />
                                  <asp:TemplateField HeaderText="Balance">
                                       <ItemTemplate>
                                            <%# Eval("Balance") %>
                                       </ItemTemplate>
                                       <EditItemTemplate>
                                            <!-- Balance Column Region [Will persist only in Edit Mode] -->
                                            <test:TestRegion ID="GridEditBalance" runat="server">
                                                 <asp:TextBox ID="newBalance" Text='<%# Eval("Balance") %>' runat="server"></asp:TextBox>
                                            </test:TestRegion>
                                            <!-- Balance Column Region -->
                                       </EditItemTemplate>
                                  </asp:TemplateField>
                             </Columns>
                             <FooterStyle BackColor="#FFFFCC" ForeColor="#330099" />
                             <RowStyle BackColor="White" ForeColor="#330099" />
                             <SelectedRowStyle BackColor="#FFCC66" Font-Bold="True" ForeColor="#663399" />
                             <PagerStyle BackColor="#FFFFCC" ForeColor="#330099" HorizontalAlign="Center" />
                             <HeaderStyle BackColor="#990000" Font-Bold="True" ForeColor="#FFFFCC" />
                        </asp:GridView>
                   </test:TestRegion>
                   <!-- The GridView Region -->
              </td>
              </tr>
         </table>
     </test:TestRegion>
     <!-- This Data Page Content -->
</asp:Content>

 

Let's put together our custom HtmlControl that we can use interact with an ASP.NET calendar control:

 

C#

  191     public class AspNetCalendar : HtmlTable
  192     {
  193         /// <summary>
  194         /// Custom ClientSideLocator for demonstration purposes only.
  195         /// The framework calls this function as part of the JavaScript call when getting/setting
  196         /// values. Put any logic in here you want to select the element/object the framework
  197         /// should act upon. The framework prepends this string to the getvalue function.
  198         /// In this example asking to get the value of the color will equate to executing this JavaScript:
  199         /// document.getElementsByTagName('{table}')[0].color
  200         /// </summary>
  201         public override string ClientSideLocator
  202         {
  203             get
  204             {
  205                 return String.Formt("document.getElementsByTagName('{0}')[{1}]", this.TagName,  this.BaseElement.TagNameIndex);
  206             }
  207         }
  208 
  209         /// <summary>
  210         /// Gets the current year/month as a DateTime object.
  211         /// </summary>
  212         /// <returns>DateTime object with the calendars current year/month. Day will always be 1.</returns>
  213         public DateTime GetDate()
  214         {
  215                 HtmlTableCell cell = this.Rows[0].Cells[0];
  216                 HtmlTable headTable = cell.ChildNodes[0].As<HtmlTable>();
  217 
  218                 string MonthYear = headTable.Rows[0].Cells[1].InnerText;
  219 
  220                 DateTime date = DateTime.Parse(MonthYear);
  221                 return date;
  222         }
  223 
  224         /// <summary>
  225         /// Moves calendar to the specified year/month.
  226         /// </summary>
  227         /// <param name="TargetDate">The target year/month to move to.</param>
  228         public void GotoDate(DateTime TargetDate)
  229         {
  230             // Must strip out the day of month and time so we compare on the year/month only.
  231             DateTime CompareDate = new DateTime(TargetDate.Year, TargetDate.Month, 1);
  232             while (GetDate() < CompareDate)
  233             {
  234                 NextMonth();
  235             }
  236             while (GetDate() > CompareDate)
  237             {
  238                 PrevMonth();
  239             }
  240         }
  241 
  242         /// <summary>
  243         /// Moves calendar to the current year/month.
  244         /// </summary>
  245         public void GotoNow()
  246         {
  247             GotoDate(DateTime.Now);
  248         }
  249 
  250         /// <summary>
  251         /// Moves the calendar to the specified year/month and then clicks on the specified day.
  252         /// </summary>
  253         /// <param name="date">The target date to be clicked on.</param>
  254         public void ClickOnDate(DateTime date)
  255         {
  256             GotoDate(date);
  257             HtmlAnchor dayElem = this.Find.ByAttributes<HtmlAnchor>("title=" + date.ToString("MMMM dd"));
  258             dayElem.Click();
  259         }
  260 
  261         /// <summary>
  262         /// Gets the calendar's month value.
  263         /// </summary>
  264         /// <returns>Month as a string.</returns>
  265         public string GetMonth()
  266         {
  267             HtmlTableCell cell = this.Rows[0].Cells[0];
  268             HtmlTable headTable = cell.ChildNodes[0].As<HtmlTable>();
  269 
  270             string MonthYear = headTable.Rows[0].Cells[1].InnerText;
  271 
  272             string[] parts = MonthYear.Split(' ');
  273             // Always displayed as 'Mmm YYYY'
  274             return parts[0];
  275         }
  276 
  277         /// <summary>
  278         /// Gets the calendar's year value.
  279         /// </summary>
  280         /// <returns>Year as a string.</returns>
  281         public string GetYear()
  282         {
  283             HtmlTableCell cell = this.Rows[0].Cells[0];
  284             HtmlTable headTable = cell.ChildNodes[0].As<HtmlTable>();
  285 
  286             string MonthYear = headTable.Rows[0].Cells[1].InnerText;
  287             string[] parts = MonthYear.Split(' ');
  288             return parts[1];
  289         }
  290 
  291         /// <summary>
  292         /// Moves the calendar to the next month.
  293         /// </summary>
  294         /// <returns>The new month as a string.</returns>
  295         public string NextMonth()
  296         {
  297             HtmlAnchor nextMonthElem = this.Find.ByAttributes<HtmlAnchor>("title=Go to the next month");
  298 
  299             nextMonthElem.Click();
  300             this.OwnerBrowser.WaitUntilReady();
  301             this.Refresh();
  302 
  303             return GetMonth();
  304         }
  305 
  306         /// <summary>
  307         /// Moves the calendar to the previous month.
  308         /// </summary>
  309         /// <returns>The new month as a string.</returns>
  310         public string PrevMonth()
  311         {
  312             HtmlAnchor nextMonthElem = this.Find.ByAttributes<HtmlAnchor>("title=Go to the previous month");
  313 
  314             nextMonthElem.Click();
  315             this.OwnerBrowser.WaitUntilReady();
  316             this.Refresh();
  317 
  318             return GetMonth();
  319         }
  320     }

 

Visual Basic

  191     Public Class AspNetCalendar
  192         Inherits HtmlTable
  193         ''' <summary>
  194         ''' Custom ClientSideLocator for demonstration purposes only.
  195         ''' The framework calls this function as part of the JavaScript call when getting/setting
  196         ''' values. Put any logic in here you want to select the element/object the framework
  197         ''' should act upon. The framework prepends this string to the getvalue function.
  198         ''' In this example asking to get the value of the color will equate to executing this JavaScript:
  199         ''' document.getElementsByTagName('{table}')[0].color
  200         ''' </summary>
  201         Public Overloads Overrides ReadOnly Property ClientSideLocator() As String
  202             Get
  203                 Return [String].Format("document.getElementsByTagName('{0}')[{1}]", Me.TagName,  Me.BaseElement.TagNameIndex)
  204             End Get
  205         End Property
  206 
  207         ''' <summary>
  208         ''' Gets the current year/month as a DateTime object.
  209         ''' </summary>
  210         ''' <returns>DateTime object with the calendars current year/month. Day will always be 1.</returns>
  211         Public Function GetDate() As DateTime
  212             Dim cell As HtmlTableCell = Me.Rows(0).Cells(0)
  213             Dim headTable As HtmlTable = cell.ChildNodes(0).[As](Of HtmlTable)()
  214 
  215             Dim MonthYear As String = headTable.Rows(0).Cells(1).InnerText
  216 
  217             Dim [date] As DateTime = DateTime.Parse(MonthYear)
  218             Return [date]
  219         End Function
  220 
  221         ''' <summary>
  222         ''' Moves calendar to the specified year/month.
  223         ''' </summary>
  224         ''' <param name="TargetDate">The target year/month to move to.</param>
  225         Public Sub GotoDate(ByVal TargetDate As DateTime)
  226             ' Must strip out the day of month and time so we compare on the year/month only.
  227             Dim CompareDate As New DateTime(TargetDate.Year, TargetDate.Month, 1)
  228             While GetDate() < CompareDate
  229                 NextMonth()
  230             End While
  231             While GetDate() > CompareDate
  232                 PrevMonth()
  233             End While
  234         End Sub
  235 
  236         ''' <summary>
  237         ''' Moves calendar to the current year/month.
  238         ''' </summary>
  239         Public Sub GotoNow()
  240             GotoDate(DateTime.Now)
  241         End Sub
  242 
  243         ''' <summary>
  244         ''' Moves the calendar to the specified year/month and then clicks on the specified day.
  245         ''' </summary>
  246         ''' <param name="date">The target date to be clicked on.</param>
  247         Public Sub ClickOnDate(ByVal [date] As DateTime)
  248             GotoDate([date])
  249             Dim dayElem As HtmlAnchor = Me.Find.ByAttributes(Of HtmlAnchor)("title=" & [date].ToString("MMMM dd"))
  250             dayElem.Click()
  251         End Sub
  252 
  253         ''' <summary>
  254         ''' Gets the calendar's month value.
  255         ''' </summary>
  256         ''' <returns>Month as a string.</returns>
  257         Public Function GetMonth() As String
  258             Dim cell As HtmlTableCell = Me.Rows(0).Cells(0)
  259             Dim headTable As HtmlTable = cell.ChildNodes(0).[As](Of HtmlTable)()
  260 
  261             Dim MonthYear As String = headTable.Rows(0).Cells(1).InnerText
  262 
  263             Dim parts As String() = MonthYear.Split(" "c)
  264             ' Always displayed as 'Mmm YYYY'
  265             Return parts(0)
  266         End Function
  267 
  268         ''' <summary>
  269         ''' Gets the calendar's year value.
  270         ''' </summary>
  271         ''' <returns>Year as a string.</returns>
  272         Public Function GetYear() As String
  273             Dim cell As HtmlTableCell = Me.Rows(0).Cells(0)
  274             Dim headTable As HtmlTable = cell.ChildNodes(0).[As](Of HtmlTable)()
  275 
  276             Dim MonthYear As String = headTable.Rows(0).Cells(1).InnerText
  277             Dim parts As String() = MonthYear.Split(" "c)
  278             Return parts(1)
  279         End Function
  280 
  281         ''' <summary>
  282         ''' Moves the calendar to the next month.
  283         ''' </summary>
  284         ''' <returns>The new month as a string.</returns>
  285         Public Function NextMonth() As String
  286             Dim nextMonthElem As HtmlAnchor = Me.Find.ByAttributes(Of HtmlAnchor)("title=Go to the next month")
  287 
  288             nextMonthElem.Click()
  289             Me.OwnerBrowser.WaitUntilReady()
  290             Me.Refresh()
  291 
  292             Return GetMonth()
  293         End Function
  294 
  295         ''' <summary>
  296         ''' Moves the calendar to the previous month.
  297         ''' </summary>
  298         ''' <returns>The new month as a string.</returns>
  299         Public Function PrevMonth() As String
  300             Dim nextMonthElem As HtmlAnchor = Me.Find.ByAttributes(Of HtmlAnchor)("title=Go to the previous month")
  301 
  302             nextMonthElem.Click()
  303             Me.OwnerBrowser.WaitUntilReady()
  304             Me.Refresh()
  305 
  306             Return GetMonth()
  307         End Function
  308     End Class

 

 

Notice how we derive from an HtmlTable. That way we can take advantage of all the functionality already built into a standard table. We'll just add all the functions useful for our calendar control which include:

  • NextMonth
  • PrevMonth
  • GetYear
  • GetMonth
  • GetDate
  • GotoDate
  • GotoNow
  • ClickOnDate

 

All these functions are fairly straightforward Telerik Testing Framework code that don't really need explanation.

 

For the purposes of demonstration there's also a ClientSideLocator override in lines 201-207 so you can see how it works and how to implement it when you want to create a more complex custom control. For our HtmlControl extenders, you can make your HtmlControl map to any object on the client side. Previously an HtmlControl object had to map to element with an HTML tag. This restriction has been removed. You can override the HtmlControl "ClientSideLocator" property and give it any JavaScript find logic to execute on the client (e.g. in ASP.NET $find("foo") ) and the HtmlControl will map itself to that object on the client. Any future calls on this object using GetValue, SetValue or CallMethod, will use this JavaScript to select the right object on the client side before getting or setting it's value, or calling your custom method on it.