4

I have such a trivial issue but I am having a hard time getting my code to properly wait for an object before moving on.

I have the following config set for my driver

session.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(60);

I was expecting this to mean that it would wait a minimum of 60 seconds before throwing errors related to element identification such as

Message: System.InvalidOperationException : An element could not be located on the page using the given search parameters.

However this is not the case. I get the error around 2 seconds in when trying to call the following command.

WindowsElement btn = session.FindElementByXPath("//Button[@Name='NEXT']");
btn.Click();

The error gets thrown on the line where I am just defining the buttons properties and not on the actual Click() method. Am I not passing the elements properties correctly? Why would the instantiation of the button also do the searching of it?

Tree55Topz
  • 1,102
  • 4
  • 20
  • 51

2 Answers2

5

There it a open issue on the winappdriver github. Take a look at this comment about it. It seems to be a Appium issue. I have no idea about the status of this issue.

Basically, this means you will have to resort to a workaround. Using Thread.Sleep(/*milliseconds*/) is a bad idea.

I implemented a while loop in a function to get a control by Automation ID like this:

    /// <summary>
    /// Gets a UI element based on a AutomationId.
    /// </summary>
    /// <param name="automationId">The AutomationId is a unique value that can be found with UI inspector tools.</param>
    /// <param name="controlName">The name of the UI element.</param>
    /// <param name="timeOut">TimeOut in milliseconds</param>
    /// <returns></returns>
    protected WindowsElement GetElement(string automationId, string controlName, int timeOut = 10000)
    {
        bool iterate = true;
        WindowsElement control = null;
        _elementTimeOut = TimeSpan.FromMilliseconds(timeOut);
        timer.Start();

        while (timer.Elapsed <= _elementTimeOut && iterate == true)
        {
            try
            {
                control = Driver.FindElementByAccessibilityId(automationId);
                iterate = false;
            }
            catch (WebDriverException ex)
            {
                LogSearchError(ex, automationId, controlName);
            }
        }

        timer.Stop();
        Assert.IsFalse(timer.Elapsed > _elementTimeOut, "Timeout Elapsed, element not found.");
        timer.Reset();

        return control;
    }

Using a loop has some advantages versus Thread.Sleep(), it's more flexible and you have much more options than simply blocking the code execution.

A few of the advantages:

  • Your test script keeps executing: Imagine your script pausing for 5 seconds while the application under test keeps going. A lot could happen in those 5 seconds your script might want to know about. But it can't, because the code execution is blocked if you use 'Thread.Sleep()'.
  • Dynamic wait: A while loop will iterate until the condition is satisfied. This makes your script continue with the test as soon as this condition is met, thus making your script run faster. E.g. you are waiting for a page to load. Thread.Sleep(5000) will assume it's OK to continue, while the loop knows it's OK to continue the test.
  • With the timer/time out combo, you can check how long the operation took (e.g. save some edits) and if it took longer than the timeout, you know it's not OK to continue.

Alternately, this code will work just as well:

protected WindowsElement GetElement(string automationId, string propertyName, int timeOut = 10000)
{
    WindowsElement element = null;
    var wait = new DefaultWait<WindowsDriver<WindowsElement>>(Driver)
    {
        Timeout = TimeSpan.FromMilliseconds(timeOut),
        Message = $"Element with automationId \"{automationId}\" not found."
    };

    wait.IgnoreExceptionTypes(typeof(WebDriverException));

    try
    {
        wait.Until(Driver =>
        {
            element = Driver.FindElementByAccessibilityId(automationId);

            return element != null;
        });
    }
    catch(WebDriverTimeoutException ex)
    {
        LogSearchError(ex, automationId, propertyName);
        Assert.Fail(ex.Message);
    }

    return element;
}

Above code will only throw a WebDriverTimeoutException instead of continuously throwing NoSuchElementException. It does not use a while loop, but I suspect wait.Until(...) is doing something similar, since WinAppDriver polls the gui every 500ms (see the PollingInterval property on the DefaultWait object.

PixelPlex
  • 749
  • 5
  • 21
  • Thank you for posting this - I was afraid I would have to resort to something like this but your use of the timer is interesting. This is safer than just using a Sleep()? – Tree55Topz May 14 '19 at 13:34
  • Yes it's safer, because you have more control. I updated my answer to address this. Please also see this blog post on sauce labs: https://saucelabs.com/blog/how-to-avoid-threadsleep-in-test-automation. 4 reasons given there. – PixelPlex May 14 '19 at 14:24
  • Take a look at [this code snipet](https://github.com/Microsoft/WinAppDriver/issues/364#issuecomment-371291769). This seems interesting because it won't throw element not found exceptions. That's always cheaper than throwing exceptions until the while condition is met :-). It also seems to be more configurable. – PixelPlex May 15 '19 at 09:50
  • The second solution is actually not working. The element just stays null and it doesnt seem to wait properly – Tree55Topz May 15 '19 at 18:20
  • Also, if an exception is found shouldnt it wait a bit so its not just hammering on the CPU? right now it will just continuously search for it and catch that rapidly – Tree55Topz May 15 '19 at 18:30
  • Yes, I wasn't really satisfied with the first solution either, because of the exact reason you mention. That's why I made a second solution. It's strange it's not working for you, I tested the code with both a existing and a non existing AutomationId and put a timer on it to see if it did go to the 10 seconds timeout. Are you sure you copied the code in the exact same way? One thing that is different in my project is that I also switch the window handler in the wait.Until statement. Also, you should see the winappdriver commandline window polling the gui every 500ms. – PixelPlex May 15 '19 at 19:31
  • The first one works fine, just need to add in some code that allows it to wait a bit using the timer once an exception is found. Maybe every 1 sec it tries – Tree55Topz May 15 '19 at 19:58
  • Also, what Timer are you using? Are you using a Stopwatch? – Tree55Topz May 15 '19 at 20:38
  • 1
    Yes, I use the Stopwatch class. It's declared in a field outside the function, so it's easy to re-use in other functions inside that class. Just make sure you don't forget to reset it. – PixelPlex May 15 '19 at 21:25
  • I found at this line: Assert.IsFalse(timer.Elapsed > _elementTimeOut, "Timeout Elapsed, element not found.") If the assertion failed, it will throw an exception and hence the next line which resets the timer will never happen. I would suggest changing the assertion above into some if/else conditional check – popsiporkkanaa Aug 30 '19 at 09:27
  • @popsiporkkanaa thanks for your suggestion. In my case, I wanted my test to fail if that condition was met. So I did that on purpose. Of course, there are use cases where this code behaviour won't be good, e.g. you expect that the ui element won't be found. – PixelPlex Aug 30 '19 at 11:50
  • For the second solution, you need to return element inside of the Until call, not element != null. Then you can return element from the GetElement function, otherwise, as @Tree55Topz noted, element will always be null. – phillyslick Sep 26 '20 at 00:15
  • @Tree55Topz The second solution does work, and it is actually better since it leverages the Until method. We don't need to reinvent the wheel, all it takes is to learn how to use the tools we already have. If you are getting null all the time it is likely because you are hitting an unhandled exception there. For instance, the code ignores WebDriverException and catches WebDriverTimeoutException, anything that is not that (e.g. InvalidOperationException or NoSuchElementException) would cause your method to try and find the elment only once and then return null. – String.Empty May 23 '22 at 21:00
  • 1
    @phillyslick Until is Until in that case. He is assigning to element whatever he gets from the FindElementByAccessibilityId() method and then returning a boolean value from the comparison of element with null. That's a valid usage for Until(). GetElement() keeps returning null most likely because exception handling is flawed there, as I mentioned in my previous comment. – String.Empty May 23 '22 at 21:21
0

I hope this helps:

public void WaitTillControlToDisplay(WindowsElement control , int Waittime=30)
{
    int startTime = 0;

    while (startTime < Waittime)
    {
        try
        {
            if (!control.Displayed)
                startTime += 1;
            else
            {
                Thread.Sleep(1000);
                return;
            }
        }
        catch (OpenQA.Selenium.WebDriverException)
        {
            Thread.Sleep(1000);
            return; // We should have Control loaded by now
        }

         Thread.Sleep(1000);
    }

    Assert.Fail("Time Out : Control - "+control+" Did not loaded within "+Waittime+" Seconds");

}

Here control should be identified before passing it to the method. Sometimes controls will be inside another container, so it’s better to identify it and pass it to the method.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
RK0703
  • 1
  • 4