Saturday, November 26, 2011

WatiN Page Model, FindBy attribute on a property throws a System.ArgumentException


While running WatiN tests in NUnit, I faced this exception as soon as I initialized a page object
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
at WatiN.Core.Composite.InitializedProperty.SetValue(Object instance, Object value)
at WatiN.Core.Composite.InitializedMember.Initialize(Object instance, IElementContainer container)
at WatiN.Core.Composite.InitializeContents(IElementContainer container)
at WatiN.Core.Page.InitializeContents()
at WatiN.Core.Page.Initialize(Document document)
at WatiN.Core.Page.CreatePage[TPage](Document document)
at WatiN.Core.Document.Page[TPage]()
System.ArgumentException : Property set method not found.

This would occur as soon as I created an instance of a page class.
PurchaseSubPage pspage = browserinstance.Page<PurchaseSubPage>();
In the automation framework, that we had used, we had modeled each and every page in the UI as a class and extended our own set of controls that would perform actions that were specific to our application.

After delving into the PurchaseSubPage class, I came across this section, in which a FindBy attribute was not removed. 
[FindBy(IdRegex="_GridView_ProductsCoveredUnsupported$")]
            protected ITAMGrid unsupportedproductscoveredresultsgrid;
            [FindBy(IdRegex = "_GridView_ProductsCoveredUnsupported")]
            public override ITAMGrid ResultsGrid
            {
                get
                {
                    return unsupportedproductscoveredresultsgrid;
                }
            }
The FindBy attribute on the ResultsGrid property was the culprit and on removing it, harmony and order were restored. 

Wednesday, May 18, 2011

When Strings don't match in WATIN

The last week while I was working with a co-worker, I ran into this strange problem with WATIN on attempting a string based constraint search on a web page.
The item in question was a Business Objects InfoView report on which the filtering capability was to be tested.
The filter was a normal drop-down list


There was nothing unusual about this element and it was rendered as an usual select list and it appeared like one of those usual select lists that could be identified by specifying a Constraint for the Title.


However, that wasn't the case. Since this was enclosed within a frame, we needed to identify the Frame object first. After having done that we tried a
We tried setting a title based constraint using the following code
SelectList uniqValueFilter = DrillBar.SelectList(t => t.Title.Contains("Drill filter on Asset Number"));
But this just would not work. An interesting thing that we found was, if we did a COPY from the Developer Toolbar window and pasted it into Visual Studio, the same line of code would work and the SelectList object would exist.
However, if we keyed it in, it would not. The copy - paste approach wasn't an option for us because we wanted to test for different reports and the SelectList would be identified using input from a XML file.

However, this provided us with a valuable clue that there was an inconsistency in the character format. We tried a few approaches that did not work, and then we decided to  inspect it further

string title = "Drill filter on Asset Number";
char[] titlefromDEVTOOLBAR = title.ToCharArray();
title = "Drill filter on Asset Number";
char[] titlewhenkeyedin = title.ToCharArray();

A good look at the character arrays inside a quick watch window gave us more details about the issue.

The "space" character when pasted from the Browser was a char 160 instead of a char 32 that was keyed in.

A simple workaround could be to replace the character 160 before doing a comparison.
SelectList uniqValueFilter = uniqValueFilter = DrillBar.SelectList(t => t.Title.Replace((char)160, ' ') == "Drill filter on Asset Number");


But an approach like this would have been a work around and not a proper solution. The character 160 was a nbsp that occurs in HTML. So, we decided to constraint the selection of the SelectList based on the presence of all the individual words that formed the string.

SelectList uniqValueFilter = DrillBar.SelectList(BuildTextConstraintforSpaces("Drill filter on Asset Number"));
private Constraint BuildTextConstraintforSpaces(string filterstring)
{
string[] tempArray = filterstring.Split(' ');
Constraint textby = Find.Any;
foreach (string temp in tempArray)
{
textby = textby.And(Find.ByText(new Regex(temp, RegexOptions.IgnoreCase)));
}
return textby;
}
This has worked as expected without causing any pain.

Friday, February 18, 2011

Web Performance Tests - Context Parameters and Dynamically generated ASP.NET mangled IDs

The application that I was coding tests for, uses dynamically generated names for all the form post parameters.
For e.g. in case of the following line of code
request6Body.FormPostParameters.Add("_hdn_ctl00_ContentPageTabSectionPlaceHolder_WebPartManager1_wp680433384_wp384219079_CompanyAdditionalInfo_FormView_CompanyAdditionalInfo_fiscalmonth_ForeignGroup_fgnGrpHelper_MonthFiscalYearEndSearch_selectionCommitted"
 , this.Context["$HIDDEN1._hdn_ctl00_ContentPageTabSectionPlaceHolder_WebPartManager1_wp680433384_wp384219079_CompanyAdditionalInfo_FormView_CompanyAdditionalInfo_fiscalmonth_ForeignGroup_fgnGrpHelper_MonthFiscalYearEndSearch_selectionCommitted"].ToString());
the, _WebPartManager1_wp680433384_wp384219079_ will change to _WebPartManager1_wp101957077_wp105468996_ or to something unpredictable on different deployments.

This occurs for about 40~50 form post parameters. However, the rest of the part of the hidden parameters is constant.
I came across a lot of articles posted related to debugging a web test that were focused on how to extract hidden fields that are not getting extracted by the web performance test recorder. In my case, the hidden fields were getting extracted just fine, but a certain portion of the names are getting mangled (perhaps due to ASP.NET and dynamic field generation).

Finally after some effort, I came up with the following workaround

1.       Record the test using the Web Test Recorder and promote the dynamic parameters in it using Visual Studio.
clip_image002[5]
Identify the container controls on the page using the IE Developer toolbar. On the Company page there were three container controls for the page sections (formlets). And, the input controls were concatenated using $ instead of an _ as in the case of the hidden fields.
2.       In the preceding request, add an extraction rule (Extract regular expression) to identify the container controls and stored them in separate context variables.
3.       Perform a search and replace across all the requests
4.       At this point, the problem is that the form post parameters would be something like
                Name: _hdn_{{AdditionalInfoFormlet}}_fiscalmonth_ForeignGroup_fgnGrpHelper_MonthFiscalYearEndSearch_selectionCommitted
Value: {{$HIDDEN1._hdn_{{AdditionalInfoFormlet}}_fiscalmonth_ForeignGroup_fgnGrpHelper_MonthFiscalYearEndSearch_selectionCommitted}clip_image004[5]
                The context parameter value within another context parameter will cause problems during the test execution from a web test (performance web test).
5.       Now, generate a coded test from this performance web test.
The code generated will be like
request6Body.FormPostParameters.Add(("_hdn_"
                            + (this.Context["AdditionalInfoFormlet"].ToString() + "_fiscalmonth_ForeignGroup_fgnGrpHelper_MonthFiscalYearEndSearch_selectionCommitte" +
                                "d")), (this.Context["$HIDDEN1._hdn_{{AdditionalInfoFormlet"].ToString() + "_fiscalmonth_ForeignGroup_fgnGrpHelper_MonthFiscalYearEndSearch_selectionCommitte" +
                    "d}}"));
6.       Search and replace for the context parameters within the context parameters to correct them
request6Body.FormPostParameters.Add(("_hdn_"+ (this.Context["AdditionalInfoFormlet"].ToString() + "_fiscalmonth_ForeignGroup_fgnGrpHelper_MonthFiscalYearEndSearch_selectionCommitted")),
                                                (this.Context["$HIDDEN1._hdn_"+ this.Context["AdditionalInfoFormlet"].ToString() + "_fiscalmonth_ForeignGroup_fgnGrpHelper_MonthFiscalYearEndSearch_selectionCommitted"]).ToString());

7.       The regular expression extract rule was something like this in the generated code
ExtractRegularExpression extractionRule5 = new ExtractRegularExpression();
extractionRule5.RegularExpression = "ctl00_ContentPageTabSectionPlaceHolder_WebPartManager1_.*?_CompanyAdditionalInfo_" +
"FormView_CompanyAdditionalInfo";
extractionRule5.IgnoreCase = false;
extractionRule5.Required = true;
extractionRule5.Index = 0;
extractionRule5.HtmlDecode = true;
extractionRule5.ContextParameterName = "AdditionalInfoFormlet";
request5.ExtractValues += new EventHandler<ExtractionEventArgs>(extractionRule5.Extract);

ExtractRegularExpression extractionRule8 = new ExtractRegularExpression();
extractionRule8.RegularExpression = "ctl00\\$ContentPageTabSectionPlaceHolder\\$WebPartManager1\\$.*?\\$CompanyAdditionalI" +
"nfo\\$FormView_CompanyAdditionalInfo\\$";
extractionRule8.IgnoreCase = false;
extractionRule8.Required = true;
extractionRule8.Index = 0;
extractionRule8.HtmlDecode = true;
extractionRule8.ContextParameterName = "AdditionalInfoInput";