Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Tips with Telerik Trouble-shooting

5.00/5 (5 votes)
13 Apr 2015CPOL13 min read 23.1K  
This article discusses some potential pitfalls with Telerik based automation test development.

This article appears in the Third Party Products and Tools section. Articles in this section are for the members only and must not be used to promote or advertise products in any way, shape or form. Please report any spam or advertising.

Introduction

Recently, I was assigned to fix an automation test suite developed with Telerik which when Running/Debugging one or multiple tests from Visual Studio would always get them done, but running the test suite as a whole by calling MStest.exe would always get stuck: after 5 consecutive tests timeout after running 30 mins as defined by the .testsettings files, all remaining tests would be bypassed. Finally, it comes to be caused by three issues:

  1. Imperfect design of ArtOfTest.WebAii.Controls.HtmlControls.HtmlWait (http://docs.telerik.com/teststudio/testing-framework/write-tests-in-code/intermediate-topics-wtc/html-control-suite-wtc/html-wait-class) and Browser.WaitForElement(http://docs.telerik.com/teststudio/advanced-topics/coded-samples/general/wait-for-element-in-code).
  2. Unwise use of ArtOfTest.WebAii.Core.Manger.ActiveBrowser.
  3. Sometime fail to click button or select option from a Dropdown list.
  4. Possible deadlock by “AutoDomRefresh” and “AutoWaitUntilReady”.

For Telerik automation testers who have experienced some unexpected exceptions, this article would explain why they could cause un-expected problems, as well as give my personal opinion/measures to avoid/fix them.

Background

The web application under test includes several “iframe” with names of “MainScreen”, “Statusbar” and etc. Telerik support this and would return the frame elements as “Browser” just like their parent IE browser. The previous codes set the IE browser “AutoDomRefresh” and “AutoWaitUntilReady” to true, even if that are already default values.

Usually, the orthodox way of running a single test is to start it from a clean environment to avoid being affected by previous tests. For web automation testing, that means the [TestInitialize] close all browsers first, then open a new one and open the target website before signing in and carry out test procedures. This pattern makes each test separated from other tests, however, it is not suitable for the project I am talking about because the login procedure costs several minutes and there are 600 tests. Unlike other projects I have worked with, this project, based on ArtOfTest.WebAii v2.0.50727, would always try to use the active browser that has logged into the system to carry out tests.

To achieve this, the [AssemblyInitialize] would start a ArtOfTest.WebAii.Core.Manager instance and only it would launch a new browser to run all tests; the [TestInitialize] would close all dialogs that could be opened during previous test. The[TestCleanup] would just close all pop-up dialog window and only leave [AssemblyCleanup()] to close all browser windows. This is a sound design if everything happened in sequence, and running multiple tests as a batch (5, 10, 50 or even 100) would usually prove it is working. However, running the whole test suite of 600 tests as a whole would be a nightmire: the MSTest.exe would always get 5 consecutive tests timeout after 30mins and then stop running the remaining ones. Debugging several tests could hardly get the failure re-occurred, and the test results would keep nothing recorded when a test runs time-out.

AutoDomRefresh not applied with HtmlWait

This problem was noticed during the login process: once the “Login” was pressed, the text of a <DIV> element changed to “Processing…” first, then disappear once the new page was loaded. Thus the function logic would watch and wait it disappear before proceed to next step, and the pseudo codes looks like this (the “StatusText” is a getter that return something like “SomeBrowser.Find.ById<HtmlControl>("statusText")”):

C#
 loginPage.LoginButton.<wbr />MouseClick();
 MenuPage menuPage = MenuPage.Get(TestBase.<wbr />GetMainBrowser());
menuPage.StatusText.Wait.<wbr />ForContentNot(FindContentType.<wbr />InnerText, "Processing...");
 menuPage.MainScreen.<wbr />RefreshDomTree();

However, when debugging with these straight-forward codes, it's lucky for me to notice that the menuPage.StatusText.Wait.ForContentNot() would fall into an endless loop: not quite often, but it did happen and then, even trying to pause it didn’t work. So I set a breakpoint within the “StatusText” getter and noticed it would be called once even if the browser has set “AutoDomRefresh” to “true”.

Immediately, I suspected Telerik framework would keep an instance of the referred element and use it to query the text continuously and thus replaced that line of codes with this:

C#
HtmlControl statusText = menuPage.StatusText;
var text = statusText.BaseElement.<wbr />InnerText;

if (!Wait.Until(() => {
    text = statusText.BaseElement.<wbr />InnerText;
    //text = menuPage.StatusText.<wbr />BaseElement.InnerText;
    return !text.Contains("Processing..."<wbr />);
}, 60 * 1000))

(The Wait.Unit() is my own simple mechanism to replace the official WebDriverWait class provided by WebDriver.Support.UI(http://www.codeproject.com/Articles/787565/Lightweight-Wait-Until-Mechanism)

No surprise for me, the same thing could happen and the test executor would be hang until the 30 minutes test time-out was triggered. This can be explained below:

  • First, the login button is clicked and that would trigger the browser to fetch a totally new page from the server.
  • Depending on the delay of the network, as well as the time cost to run “MenuPage menuPage = MenuPage.Get(TestBase.GetMainBrowser())”, which I guess was used to introduce some delay by previous developers to get the menuPage referring the new content by waiting the browser to be ready before refreshing the DOM tree.
  • Depends on the delay of the network, responsiveness of the server, there could be two results:
    1. Very likely, the “menuPage” would be updated after the new page is loaded by the browser, then “StatusText” would refer a fresh HtmlControl and then the Wait.ForContentNot() would compare the right content shown on the browser just as expected.
    2. Sometime, just as I have described, it seems the “menuPage.StatusText” would be stale and the InnerText would never be the text we see from the IE browser.

From the coding perspective, the previous developer was fully awared of this possiblility and have taken some measures to prevent it from happening by:

  • Instead of using a variable to keep the instance “HtmlControl statusText” as I did to reconstruct the problem, the “StatusText” is defined as a getter of “return Statusbar.Find.ById<HtmlControl>("statusText")”;
  • The “Statusbar” by itself, is also a getter including codes like “Browser.RefreshDomTree();returnBrowser.Frames["Statusbar"];”

So if the Telerik framework does apply “AutoDomRefresh=true” with its HtmlWait mechanism, then the two getters shall be called in sequence and always get the right status text for further evaluation. However, as a property of a HtmlControl Instance, HtmlWait would not try to check its validity before performing further steps.

Generally speaking, I prefer the “StaleElementReferenceException” (http://docs.seleniumhq.org/exceptions/stale_element_reference.jsp) of Selenium that would inform you what is wrong with the running test. Unlike CodedUI whose UITestControl.Exists property could be used to detect the status of HtmlControl conveniently, there is no similar function/property; and to make it worse, a state element that shall be disposed would keep returning some valid values like “InnerText” without throwing any Exception, that is very misleading!

Anyway, be cautious with the use of HtmlWait. For me, I used the following codes to replace the “menuPage.StatusText.Wait.ForContentNot(FindContentType.InnerText, "Processing...");”:

C#
if(Wait.Until(()=>!menuPage.<wbr />StatusText.BaseElement.<wbr />InnerText.Contains("<wbr />Processing..."), 240 * 1000))
    Trace.WriteLine("'Processing..<wbr />.' is gone!");
Else
    ...

Thought I have thought this problem is only related with HtmlWait, it came out that usingBrowser.WaitForElement() could result in timeout even during debugging the codes manually. For example, codes like

C#
somePage.<wbr />MainScreen.RefreshDomTree();
somePage.MainScreen.<wbr />WaitForElement (120000, "name=elementName");

The “MainScreen” refers some “iframe” element and is an instance of "Browser",  but it would fall into deadlock even I am debugging the codes by pressing F10. Possibly, the MainScreen instance used for calling WaitForElement() would fail to refresh its DOM tree when waiting for an Element and a quick fix is to replace “WaitForElement” with an extension method of “WaitElementFound”:

C#
public static bool WaitElementFound(this ArtOfTest.WebAii.Core.Browser browser, int timeout, paramsstring[] findClauses)
{
    Stopwatch stopWatch = Stopwatch.StartNew();
    return Wait.Until(() =>
    {
        var element = browser.Find.ByExpression(<wbr />findClauses);
        if (element != null)
            return true;

        Debug.WriteLine("{0}: no element found with '{1}'", stopWatch.Elapsed,string.Concat(findClauses));
        return false;
    }, timeout);
}

ActiveBrowser may not be ACTIVE

Watching from the recorded video, I noticed before the “TimeOut” happen, the last Passed test ended with a Dialog window popping up and being closed immediately.

Noticing that within the [TestInitialize], the Manager.ActiveBrowser was kept as a local reference and referred by all functions/pages of the test, I found someone has mentioned “Manager.ActiveBrowser isn’t working”(http://www.telerik.com/forums/manager-activebrowser-isn-t-working). Possibly, there is a bug with the Manager: its ActiveBrowser would refer the dialog window that was closed and should have been disposed.

To prove this guess, I added some codes to an existing test to watch the changes of Manager.Browsers:

C#
InternetExplorerActions ieActions = ((InternetExplorerActions)(TheBrowser.Actions));
ieActions.ConnectIEDialog("Enquiry", 1000);

Browser thePopup = TestBase.MyManager.Browsers.Single(f => f.Window.Caption.Contains("Enquiry")));
EnquiryPage inputPage = EnquiryPage.Get(thePopup);
inputPage.AccountNo.Text = isCustomer ? CIFNo : accountNo;

//Click Transmit Button of the inputPage dialog would open another Dialog, which is shown immediately
inputPage.ClickTransmitButton();

//Click to close the dialog identified by thePopup
inputPage.CloseButton.InvokeEvent(ScriptEventType.OnClick);

bool isWindowExists, hasWindowProcessExisted;
int browserCount = 0;
Stopwatch stopWatch = Stopwatch.StartNew();
//Wait 20s to see what will happen
Wait.Until(() =>
{
    isWindowExists = thePopup.Window.Exists;
    hasWindowProcessExisted = thePopup.Window.OwnerProcess.HasExited;
    browserCount = TestBase.MyManager.Browsers.Count;
    Debug.WriteLine("{0}(0x{4:X}): isWindowExists = {1}, hasWindowProcessExisted = {2}, browserCount = {3}",
        stopWatch.Elapsed, isWindowExists, hasWindowProcessExisted, browserCount, thePopup.Window.Handle);
    return !isWindowExists && !hasWindowProcessExisted;
}, 60 * 1000);
Debug.WriteLine("{0}(0x{1:X}): Finished this first Wait.Until()", stopWatch.Elapsed, thePopup.Window.Handle);

Browser thePopupResult = null;
Wait.Until(() =>
{
    ieActions.ConnectIEDialog("Details", 0);
    isWindowExists = thePopup.Window.Exists;
    hasWindowProcessExisted = thePopup.Window.OwnerProcess.HasExited;
    browserCount = TestBase.MyManager.Browsers.Count;
    Debug.WriteLine("{0}(0x{4:X}): isWindowExists = {1}, hasWindowProcessExisted = {2}, browserCount = {3}",
        stopWatch.Elapsed, isWindowExists, hasWindowProcessExisted, browserCount, thePopup.Window.Handle);
    thePopupResult = TestBase.MyManager.Browsers.FirstOrDefault(f => f.Window.Caption.Contains("Details"));
    return thePopupResult != null;
}, 30 * 1000);

isWindowExists = thePopupResult.Window.Exists;
hasWindowProcessExisted = thePopupResult.Window.OwnerProcess.HasExited;
browserCount = TestBase.MyManager.Browsers.Count;
Debug.WriteLine("{0}(0x{4:X}): isWindowExists = {1}, hasWindowProcessExisted = {2}, browserCount = {3}",
    stopWatch.Elapsed, isWindowExists, hasWindowProcessExisted, browserCount, thePopupResult.Window.Handle);

The second dialog has actually already displayed above the first dialog when it runs to "inputPage.CloseButton.InvokeEvent()", but the output shows something quite different:

InvokeEvent(ScriptEventType.OnClick) on Transmit button on Dialog
00:00:00.0056507(0x66904): isWindowExists = True, hasWindowProcessExisted = False, browserCount = 2
00:00:01.0294893(0x66904): isWindowExists = False, hasWindowProcessExisted = False, browserCount = 1
00:00:01.0295843(0x66904): Finished this first Wait.Until()
00:00:01.4008161(0x66904): isWindowExists = False, hasWindowProcessExisted = False, browserCount = 1
00:00:01.4049222(0x787832): isWindowExists = True, hasWindowProcessExisted = False, browserCount = 2
BaseTestCleanup

The lines with time stamp of "00.0056507" and "01.4008161" demonstrated the problem: when the first dialog has been closed and its process has existed, the thePopup.Window.Exists still shows it as alive and the ActiveBrowser of the Manager would definitely point to this closed browser. When the first Wait.Until() finished running, the second dialog has been there for over 1 second (it might be halted by Telerik intentionally) and the ConnectIEDialog has definitely succeeded, but browserCount is still 1. Apparently, Telerik has taken some measures to protect "Browsers" (by some lock?), but that needs to be improved.

More interestingly, after commenting the first Wait.Until(), though it should still work, an exception is received showing "System.ComponentModel.Win32Exception: Access is denied". Just a guess, the second "ConnectIEDialog" triggers the update of the "Browsers", but it is under changing to remove the instance of the first dialog.

To fix this problem, let the thread sleep for several seconds shall be OK, but for me, the simplest solution is to refer “MyManager.Browsers[0]” instead of “MyManager.ActiveBrowser”. Because in[AssemblyInitialize], after closing all existing IE browsers, a new browser is launched with

C#
MyManager.LaunchNewBrowser(Bro<wbr />wserType.InternetExplorer, true, ProcessWindowStyle.Maximized);

Thus  “MyManager.Browsers[0]” should always refer to this browser that would be used to run the whole test suite and using it and save it to a static variable would be safer in this case.

Noticing the Window.OwnerProcess is encapsulating the System.Diagnostics.Process, the "HasExisted" is a very reliable property to detect the staleness of Browsers, thus following methods could be used to evaluate if the Browser or its Children elements are valid or not:

C#
public static bool DoesExist(this ArtOfTest.WebAii.Core.Browser browser)
{
    return !browser.Window.OwnerProcess.HasExited;
}

public static bool DoesExist(this ArtOfTest.WebAii.Controls.HtmlControls.HtmlControl control)
{
    return control.OwnerBrowser.DoesExist();
}

However, please be awared that the second one is not safe if the browser has refreshed its content, so it is just a convenient way to detect the validity of the Browser container.

Selecting and Clicking with confirming and retrying

There are many tests of this project need to select one option from a select control by typing something, if the input text is not correct, then following steps would fail. Previously, it is implemented with an extension method of HtmlControl:

C#
public static void TypeText(this HtmlControl target, string text)
{
     if (target == null)
         Microsoft.VisualStudio.<wbr />TestTools.UnitTesting.Assert.<wbr />Fail("Failed to locate the element to enter text of " + text);

     target.MouseClick();
     System.Windows.Forms.SendKeys.<wbr />SendWait(text);
 }

However, from time to time, the entered text would be different than expected. For example, the “AB” could turn out to be “ABB” or “B”. To ensure this operation is successfully, I have instead implement this function:

C#
public static bool SelectByTyping(string text, params string[] findClauses)
 {
     HtmlSelect theSelect;
     string selectedOptionText = null;

     if (findClauses==null || findClauses.Length == 0)
         throw new ArgumentNullException("<wbr />findClauses must be used to locate the element dynamically!");

     //trying 3 times to find element and select by typing
     int attempt = 3;
     do
     {
         //Wait the element found in the MainScreen frame, if failed in 10s, continue to try
         if (!TheMainScreen.<wbr />WaitElementFound(10 * 1000, findClauses))
         {
             Trace.WriteLine("Failed to locate the select after 10 seconds with findClauses of " +String.Join(", ", findClauses));
             continue;
         }

         theSelect = TheMainScreen.Find.<wbr />ByExpression<HtmlSelect>(<wbr />findClauses);
         if (theSelect == null)
         {
             Assert.Fail("Failed to cast the element to select with " + String.Join(", ", findClauses));
         }

         theSelect.ScrollToVisible();
         theSelect.MouseClick();

         //Wait 500ms before typing one character after another
         TheMainScreen.Desktop.<wbr />KeyBoard.TypeText(text, 500, 10);

         //Make the select lost focus, then wait 1s to enable web app update its content
         theSelect.InvokeEvent(ScriptEv<wbr />entType.OnBlur);
         System.Threading.Thread.Sleep(<wbr />1000);

         //The parent container would refresh its dom before searching to get the fresh element
         theSelect = TheMainScreen.Find.<wbr />ByExpression<HtmlSelect>(<wbr />findClauses);

         var selectedOption = theSelect.SelectedOption;
         selectedOptionText = selectedOption.Text;

         if (selectedOptionText.<wbr />StartsWith(text))
             return true;

         attempt--;
     } while (attempt > 0);

     Trace.WriteLine("Failed 3 times to find the select and choose option by typing '{0}', now the selectedOptionText is " + selectedOptionText);
     return false;
 }
 

Notice that the HtmlSelect was searched twice and “TheMainScreen”, with "RefreshDomTree" embedded as discussed in next section, would ensure it is fresh (this line of code could be implemented in the MainScreen that is a getter instead). In this way, choosing option of a select element would be much more reliable than previous implementation and the verification of the selecting result could be encapsulated as another function to be used to replace previous codes.

C#
public void AssertSomeCode(string someCode)
{
     Assert.IsTrue(<span style="font-size: 9.5pt; font-family: Consolas; color: black; background: white;">SelectByTyping(<wbr />someCode, </span><span style="font-size: 9.5pt; font-family: Consolas; color: rgb(163, 21, 21); background: white;">"name=someID"</span><span style="font-size: 9.5pt; font-family: Consolas; color: black; background: white;">)</span>);
}

For other less used select operations, the following function was used only with the failed tests:

C#
public void AssertSelectByTyping(string text, params string[] findClauses)
{
    Assert.IsTrue(SelectByTyping(<wbr />text, findClauses));
}

Another similar problem is that all operations of the tests need to click a "Transmit" button after filling text fields and selecting options, then the status text would show something like "Processing..." to indicate there is something triggered by clicking it. However, both HtmlControl.Click() or Browser.Desktop.Mouse.Click() could fail to click even the video showed the mouse did move over the button. I have witnessed this phenomena with both Selenium and CodedUI, so I suspect it comes from Windows system. Actually, I did also notice that some time when I click something on a web page, the browser would failed to react as it should be. To replace thousands of lines of codes like "SomePage.TransmitButton.Click()", a static method is defined in the base page like this:

C#
public bool ClickTransmitButton(int attempt=3)
{
    bool result = false;

    do
    {
        result = clickTransmitButton();
        if (result)
        {
            System.Threading.Thread.Sleep(<wbr />3000);
            return result;
        }

        attempt--;
        Trace.WriteLine("Failed to click Transmit Button once.");
    }while(attempt>0);

    return false;
}

private bool clickTransmitButton()
{
    HtmlControl transmit = TransmitButton;

    if (transmit == null)
    {
        Trace.WriteLine("Failed to locate the Transmit button.");
        return false;
    }

    bool isDisabled = transmit.GetValue<bool>("<wbr />disabled");
    //If the Transmit button is disabled and it failed to be enabled within 10 seconds

    if(isDisabled && !Wait.Until(()=>!transmit.<wbr />GetValue<bool>("disabled"), 10*1000))
    {
        isDisabled = transmit.GetValue<bool>("<wbr />disabled");

        if(!isDisabled)
        {
            Trace.WriteLine("No meaning to click the Transmit button when it is still disabled.");
            Trace.WriteLine("IsEnabled = " + transmit.IsEnabled);
            return false;
        }
    }

    string currentStatusText = MenuPage.GetStatusText();

    //Click the Transmit with Click()
    transmit.Click();

    //Wait 10s to see if  the status text would change, Otherwise, Wait 3 seconds to see if the transmit is disabled
    Func<bool> statusTextChangedOrDisabled = () => Wait.Until(() =>
        {
            var newStatusText = MenuPage.GetStatusText();
            return newStatusText != currentStatusText;
        }, 10 * 1000) || Wait.Until(() => !transmit.GetValue<bool>("<wbr />disabled"), 3 * 1000);

    if (statusTextChangedOrDisabled()<wbr />)
        return true;

    //Now it seems the clicking doesn't really happen, try again by triggering JavaScript directly
    transmit.InvokeEvent(ScriptEve<wbr />ntType.OnClick);

    if (statusTextChangedOrDisabled()<wbr />)
        return true;

    //There is no signal showing the clicking works
    return false;
}

It is no meaning to explain the above codes here, but by replacing the thousands of ".TransmitButton.Click()" of "SomePage.TransmitButton.Click()" with “.ClickTransmitButton()”, a much more comprehensive state checking and trying multiple times when Clicking failed could be performed.

In addition, if I have to click an element with a single line of code, compared with HtmlControl.Click(), I would prefer “InvokeEvent(ScriptEventType.OnClick)”, that is equal to run JavaScript directly on the element, because the latter would trigger the consequence of a clicking on the element directly, not only avoiding of lost control of your mouse, but also achieving higher possibility to do the clicking.

AutoDomRefresh + AutoWaitUntilReady = Dead Loop?

Even after fixing quite some bugs besides of the issues mentioned above, I could still not explain why Telerik could be hang until one day, after adding some try{}catch{} to print out the stack trace.

There is one test failed with Stack Trace like this:

C#
ArtOfTest.WebAii.Core.Browser.<wbr />ExecuteCommandInternal(<wbr />BrowserCommand request)
ArtOfTest.WebAii.Core.Browser.<wbr />ExecuteCommand(BrowserCommand request, Boolean performDomRefresh, BooleanwaitUntilReady)
ArtOfTest.WebAii.Core.Browser.<wbr />RefreshDomTree()
ArtOfTest.WebAii.Core.<wbr />FramesCollection.<wbr />EnsureDomLoaded(Browser browser)
ArtOfTest.WebAii.Core.<wbr />FramesCollection.get_Item(<wbr />String name)
…

It is quite straightforward and seems that the Browser failed to run RefreshDomTree() for some reason, but there are something unusual in the whole Error Message listed below:

Test method xxxxx.yyyyyyyyyyyyyyyyyy threw exception:

ArtOfTest.WebAii.Exceptions.<wbr />ExecuteCommandException: ExecuteCommand failed!

InError set by the client. Client Error:

System.InvalidCastException: Unable to cast COM object of type 'mshtml.HTMLDocumentClass' to interface type 'mshtml.IHTMLDocument2'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{332C4425-26CB-11D0-B483-<wbr />00C04FD90119}' failed due to the following error: Old format or invalid type library. (Exception from HRESULT: 0x80028019 (TYPE_E_UNSUPFORMAT)).

   at mshtml.HTMLDocumentClass.<wbr />IHTMLDocument2_get_frames()

   at ArtOfTest.InternetExplorer.<wbr />IEFramesCollection.<wbr />BuildFramesInternal(<wbr />IHTMLDocument2 topDocument, String parentID, Dictionary`2 frames)

   at ArtOfTest.InternetExplorer.<wbr />IEFramesCollection.<wbr />BuildFrames(IHTMLDocument2 parentDocument)

   at ArtOfTest.InternetExplorer.<wbr />IECommandProcessor.<wbr />SetTargetDocument(<wbr />BrowserCommand& request, IHTMLDocument2 document)

   at ArtOfTest.InternetExplorer.<wbr />IECommandProcessor.<wbr />ProcessCommand(WebBrowserClass ieInstance, BrowserCommand request, IHTMLDocument2 document)

BrowserCommand (Type:'Information',Info:'<wbr />DocumentMarkup',Action:'<wbr />NotSet',Target:'null',Data:'',<wbr />ClientId:'Client_74d12484-<wbr />bd3d-4a02-b3cc-66e6bfb47c02',<wbr />HasFrames:'False',FramesInfo:'<wbr />',TargetFrameIndex:'0',<wbr />InError:'True',Response:'<wbr />System.InvalidCastException: Unable to cast COM object of type 'mshtml.HTMLDocumentClass' to interface type 'mshtml.IHTMLDocument2'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{332C4425-26CB-11D0-B483-<wbr />00C04FD90119}' failed due to the following error: Old format or invalid type library. (Exception from HRESULT: 0x80028019 (TYPE_E_UNSUPFORMAT)).

   at mshtml.HTMLDocumentClass.<wbr />IHTMLDocument2_get_frames()

   at ArtOfTest.InternetExplorer.<wbr />IEFramesCollection.<wbr />BuildFramesInternal(<wbr />IHTMLDocument2 topDocument, String parentID, Dictionary`2 frames)

   at ArtOfTest.InternetExplorer.<wbr />IEFramesCollection.<wbr />BuildFrames(IHTMLDocument2 parentDocument)

   at ArtOfTest.InternetExplorer.<wbr />IECommandProcessor.<wbr />SetTargetDocument(<wbr />BrowserCommand& request, IHTMLDocument2 document)

   at ArtOfTest.InternetExplorer.<wbr />IECommandProcessor.<wbr />ProcessCommand(WebBrowserClass ieInstance, BrowserCommand request, IHTMLDocument2 document)')

InnerException: none.


TestCleanup method Bancslink.Test.Tests.<wbr />LoanTests.TestCleanup threw exception. ArtOfTest.WebAii.Exceptions.<wbr />ExecuteCommandException: ArtOfTest.WebAii.Exceptions.<wbr />ExecuteCommandException: ExecuteCommand failed!

InError set by the client. Client Error:

System.InvalidCastException: Unable to cast COM object of type 'mshtml.HTMLDocumentClass' to interface type 'mshtml.IHTMLDocument2'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{332C4425-26CB-11D0-B483-<wbr />00C04FD90119}' failed due to the following error: Old format or invalid type library. (Exception from HRESULT: 0x80028019 (TYPE_E_UNSUPFORMAT)).

   at mshtml.HTMLDocumentClass.<wbr />IHTMLDocument2_get_frames()

   at ArtOfTest.InternetExplorer.<wbr />IEFramesCollection.<wbr />BuildFramesInternal(<wbr />IHTMLDocument2 topDocument, String parentID, Dictionary`2 frames)

   at ArtOfTest.InternetExplorer.<wbr />IEFramesCollection.<wbr />BuildFrames(IHTMLDocument2 parentDocument)

   at ArtOfTest.InternetExplorer.<wbr />IECommandProcessor.<wbr />SetTargetDocument(<wbr />BrowserCommand& request, IHTMLDocument2 document)

   at ArtOfTest.InternetExplorer.<wbr />IECommandProcessor.<wbr />ProcessCommand(WebBrowserClass ieInstance, BrowserCommand request, IHTMLDocument2 document)

BrowserCommand (Type:'Information',Info:'<wbr />DocumentMarkup',Action:'<wbr />NotSet',Target:'null',Data:'',<wbr />ClientId:'Client_74d12484-<wbr />bd3d-4a02-b3cc-66e6bfb47c02',<wbr />HasFrames:'False',FramesInfo:'<wbr />',TargetFrameIndex:'-1',<wbr />InError:'True',Response:'<wbr />System.InvalidCastException: Unable to cast COM object of type 'mshtml.HTMLDocumentClass' to interface type 'mshtml.IHTMLDocument2'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{332C4425-26CB-11D0-B483-<wbr />00C04FD90119}' failed due to the following error: Old format or invalid type library. (Exception from HRESULT: 0x80028019 (TYPE_E_UNSUPFORMAT)).

   at mshtml.HTMLDocumentClass.<wbr />IHTMLDocument2_get_frames()

   at ArtOfTest.InternetExplorer.<wbr />IEFramesCollection.<wbr />BuildFramesInternal(<wbr />IHTMLDocument2 topDocument, String parentID, Dictionary`2 frames)

   at ArtOfTest.InternetExplorer.<wbr />IEFramesCollection.<wbr />BuildFrames(IHTMLDocument2 parentDocument)

   at ArtOfTest.InternetExplorer.<wbr />IECommandProcessor.<wbr />SetTargetDocument(<wbr />BrowserCommand& request, IHTMLDocument2 document)

   at ArtOfTest.InternetExplorer.<wbr />IECommandProcessor.<wbr />ProcessCommand(WebBrowserClass ieInstance, BrowserCommand request, IHTMLDocument2 document)')

InnerException: none.

The same set of methods, as highlighted in bold font, repeated 4 times!

It seems to me there was a dead loop happened within Telerik is executing “RefreshDomTree()” when both “AutoDomRefresh” and “AutoWaitUntilReady” of the “Manger” instance used to manage the running test browsers are set to “true”(and by default, they would be true): very likely, the RefreshDomTree() would trigger some internal waitUntilReady() and which would then run RefreshDomTree in some way. Consequently, if there is no loop detection or intentional delay before refresh/wait, then of course it is possible for Telerik users to experience intermittent exceptions when it fall into an endless loop. To make the situation worse, these two settings would also be true for "Browser" referring frame!

Realizing this potential defect, I have to define some static Browser in the BasePage class, set their “AutoWaitUntilReady” to be false, but insert a "RefreshDomTree()" before returning them to locate their children elements as the sample codes below:

C#
private static Browser getFreshFrame(Browser frame, string frameName)
{
    if (frame == null || frame.Window == null || !frame.IsConnected)
    {
        if (frame != null)
        {
            Debug.WriteLine("frame.IsConnected = {0}, frame.ClientId={1}, frame.Window={2}", frameName, frame.ClientId, frame.Window);
        }
        var ie = TestBase.Keywords.TheBrowser;
        ie.RefreshDomTree();
        if(!ie.Frames.ContainsKey(frameName))
            Assert.Fail("Failed to get the frame of " + frameName);

        frame = ie.Frames[frameName];
        frame.AutoDomRefresh = false;
        frame.AutoWaitUntilReady = false;
    }

    frame.RefreshDomTree();
    return frame;
}

public static Browser theMainScreen = null;
public static Browser TheMainScreen
{
    get
    {
        return getFreshFrame(theMainScreen, "MainScreen");
    }
}

public Browser MainScreen
{
    get
    {
        return TheMainScreen;
    }
}
The "MainScreen" is defined in BasePage class, thus after deleting the duplicated ones in the derived page, most of codes could be kept unchanged. 

Within the derived page constructors, instead of calling "WaitUntilReady()" before and after calling "RefreshDomTree()", the BasePage constructor could wait something happen, like the status text showing the page is loaded, like this:

C#
public PageBase(ArtOfTest.WebAii.Core.Browser container)
{
    if (null == container)
        throw new System.ArgumentNullException("container");

    container.AutoWaitUntilReady = false;
    container.AutoDomRefresh = false;

    WaitProcessingDone();
    if(container.IsIEDialog)
    {
        container.WaitUntilReady();
    }
    System.Threading.Thread.Sleep(2000);

    container.RefreshDomTree();

    //Setting theMainScreen to be null, would force TheMainScreen to search the frame again
    theMainScreen = null;

    this._Browser = container;
}
As a common pattern, every time when the browser loads some different content/frame, a page object would be constructed which would call the above constructor and that would usually happen after the status bar showing a positive signal or the IE browser is ready for user input. Then when the test script begins any operations by locating them first from their "MainScreen.Find", accessing the "TheMainScreen" getter would trigger the "container" stored as "_Browser" to refresh DOM tree and fetch the latest frame with latest children elements.
 
Compared with the inherited "AutoWaitUntilReady" mechanism, that would lead to the "WaitUntilReady()" of a time-consuming page loading to be timeout just as a normal finding operation after the pages loaded, the above approach concerns more of the actual "READY" signal and avoid unnecessary waiting and refreshing that could potentially fall into an endless loop. As a result, the 600 tests run continuously for several days, without any "Timeout" happened again.

To make the tests more robust, the [TestInitialize] would detect if the previous test is time-out and if it is, all browser of the "Manager" would be removed and all IE processes would be killed to get a clear environment to start following tests.

Points of Interest

AutoDomRefresh and AutoWaitUntilReady could get your convenience in most of time, but without taking measures to cope with their interleaving operation, you might suffer from intermittent exception and get no clues to find out why. Unfortunately, this problem could exist in other testing framework like CodedUI and WebDriver as well, so be careful; for Telerik, at least turn one of them to "false".

With no evidence, but the Find might also be implemented with both features that could explain why VS could be hang when hovering mouse over a control instance during debugging mode. If this is true, then the Timeout would be unavoidable, just with a much smaller possibility compared with both features activated everywhere.

In addition, I am very suspicous of feasibility of the popular "Page Object" model that could not only define methods and properties redanduntly, but also keep state element instances. For UI automation testing where there are usually milliseconds or even seconds of processing delay, keeping the logical path/means to search might be much more robust and efficient.

History

Keep a running update of any changes or improvements you've made here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)