1
\$\begingroup\$

I am trying to use Selenium for website automation testing tasks and I am new to Selenium testing framework. The the situation I faced is to wait the website components loading and then do the related operations with the target components. The WaitFor method with xpath and waitingTimes input parameters are designed as below.

The experimental implementation

private static bool WaitFor(IWebDriver driver, string xpath, uint waitingTimes = 100)
{
    log.Info("Wait for " + xpath);
    uint count = 0;
    while (IsElementExists(driver, xpath) == false)
    {
        if (count >= waitingTimes)
        {
            return false;
        }
        Thread.Sleep(1000);
        count++;
    }
    return true;
}

private static bool IsElementExists(IWebDriver driver, string xpath)
{
    try
    {
        var element = driver.FindElement(By.XPath(xpath));
        return true;
    }
    catch (Exception ex)
    {
        log.Info(ex.Message);
    }
    return false;
}

The usage of WaitFor method:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Threading;
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Repository.Hierarchy;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

namespace WaitForMethodSeleniumWebDriver
{
    class Program
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private static BackgroundWorker backgroundWorkerForLogging = new BackgroundWorker();

        static void Main(string[] args)
        {
            LogHandler();
            log.Info("Main method");
            IWebDriver driver = new ChromeDriver(@".\");
            driver.Url = "https://codereview.stackexchange.com/";
            WaitFor(driver, ".//*[@id='content']");
            //    perform next step operation
            //    ...
            driver.Close();
            driver.Quit();
        }

        //    Reference: https://stackoverflow.com/a/4172968/6667035
        private static void LogHandler()
        {
            Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository(System.Reflection.Assembly.GetEntryAssembly());
            XmlConfigurator.Configure(hierarchy, new FileInfo("log4net.config"));

            FileAppender fileAppender = new FileAppender();
            fileAppender.AppendToFile = true;
            fileAppender.LockingModel = new FileAppender.MinimalLock();

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder = stringBuilder.Append("log").Append(DateTime.UtcNow.ToString("_MM-dd-yyyy_hh_mm_ss")).Append(".txt");
            fileAppender.File = stringBuilder.ToString();
            log4net.Layout.PatternLayout pl = new log4net.Layout.PatternLayout();
            pl.ConversionPattern = "%d [%2%t] %-5p [%-10c]   %m%n";
            pl.ActivateOptions();
            fileAppender.Layout = pl;
            fileAppender.ActivateOptions();

            BasicConfigurator.Configure(fileAppender);
            
            //    Reference: https://stackoverflow.com/a/2077767/6667035
            backgroundWorkerForLogging.DoWork += (sender, e) =>
            {
                UDPListener();
            };
            // Start BackgroundWorker
            backgroundWorkerForLogging.RunWorkerAsync();
        }

        // launch this in a background thread
        private static void UDPListener()
        {
            System.Net.IPEndPoint remoteEndPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Any, 0);
            var udpClient = new System.Net.Sockets.UdpClient(10001);

            while (true)
            {
                var buffer = udpClient.Receive(ref remoteEndPoint);
                var loggingEvent = System.Text.Encoding.Unicode.GetString(buffer);
                Console.WriteLine(loggingEvent);
            }
        }


        private static bool WaitFor(IWebDriver driver, string xpath, uint waitingTimes = 100)
        {
            log.Info("Wait for " + xpath);
            uint count = 0;
            while (IsElementExists(driver, xpath) == false)
            {
                if (count >= waitingTimes)
                {
                    return false;
                }
                Thread.Sleep(1000);
                count++;
            }
            return true;
        }

        private static bool IsElementExists(IWebDriver driver, string xpath)
        {
            try
            {
                var element = driver.FindElement(By.XPath(xpath));
                return true;
            }
            catch (Exception ex)
            {
                log.Info(ex.Message);
            }
            return false;
        }
    }
}

All suggestions are welcome.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

WaitFor

  • This method name is kinda weird for me
    • If I would be your API's consumer then I would except from this naming that it would return with a IWebElement instance (or null) rather than a boolean value.
  • But, if you want to stick to this existence check API then I would suggest:
static bool CheckElementExistence(this IWebDriver driver, string elementXPath, byte waitUntilInSeconds = 10)
  • I've renamed your xpath parameter to a more meaningful one
  • The waitingTimes in my opinion is really bad parameter
    • You have to know some implementation detail (Thread.Sleep(1000)) to be able to determine this parameter's value
    • That's why I would suggest to use some other concept, like waitUntil
      • It is usually a good practice to include the time unit in the name as well to be able to use the API without scrutinizing the documentation (whether it is a milliseconds or seconds)
  • IsElementExists(driver, xpath) == false: I know some people does not like the usage of logical negation operator because it is easy to oversee, but you can rewrite your loop to avoid the usage of == false
private static bool CheckElementExistence(this IWebDriver driver, string elementXPath, byte waitUntilInSeconds = 10)
{
    log.Info("Checking existence of " + elementXPath);
    byte remainingSeconds = waitUntilInSeconds;
    while (remainingSeconds > 0)
    {
        if (DoesElementExist(driver, elementXPath)) return true;
                
        Thread.Sleep(1000);
        remainingSeconds--;
    }
    return false;
}
  • It might make sense to make your method async and use Task.Delay rather than Thread.Sleep

IsElementExists

  • I would suggest to rename your method to DoesElementExist
  • I would also recommend to handle only NoSuchElementException rather than any Exception.
    • For example if the provided xpath is malformed or null the FindElement might throw ArgumentException (that's just an assumption, I don't know). You can handle that case differently
  • var element: if you are not using this variable then you can simple use the discard operator
private static bool DoesElementExist(IWebDriver driver, string elementXPath)
{
    try
    {
        _ = driver.FindElement(By.XPath(elementXPath));
        return true;
    }
    catch (NoSuchElementException ex)
    {
        log.Info(ex.Message);
        return false;
    } 
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ from your DoesElementExist method there is a small trap, FindElement uses the drivers search wait time and sit there until it times out or finds it. if your timeout in in minuets and the calling functions timeout may be in assumed seconds. but remainingSeconds in your example is just a counter for number of attempts to recheck. as is .... if you set waitUntilInSeconds to 20, driver timeout is 30 seconds, then if we never find the control, we sit here 620 seconds \$\endgroup\$ Commented Sep 3, 2021 at 16:37
  • \$\begingroup\$ Quite frankly I haven't used the .net library. I've used the js lib several years ago. I assumed that the FindElement is instant, it does not wait for a particular time. With that in mind my design has that problem that you have described. Thanks for pointing out. Is there a FindElementAsync method which receives a cancellationToken? \$\endgroup\$ Commented Sep 3, 2021 at 17:27
  • \$\begingroup\$ the closest thing for immediate results is FindElements which returns a collection. then check the count. \$\endgroup\$ Commented Sep 3, 2021 at 17:29
2
\$\begingroup\$

This is a bit of a reimagining, but I think it will help.

Selenium actually has a smart wait helper package called DotNetSeleniumExtras.WaitHelpers.

With it, you can greatly simplify your code to just:

private void WaitFor(IWebDriver driver, By by, TimeSpan wait)
{
    var smartWait = new WebDriverWait(driver, wait);
    smartWait.Until(ExpectedConditions.ElementExists(by));
}

Usage:

WaitFor(driver, By.XPath(".//*[@id='content']"), TimeSpan.FromSeconds(10));

This way, you are able to keep all of the parameters as open as you need them to be. The driver is always necessary. The search criteria still let you use any available search patterns—e.g., by CSS class or by id—and the explicit timespan lets you understand the delay time at a glance.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.