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.
2 Answers 2
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 (ornull
) rather than aboolean
value.
- If I would be your API's consumer then I would except from this naming that it would return with a
- 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)
- You have to know some implementation detail (
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 thanThread.Sleep
IsElementExists
- I would suggest to rename your method to
DoesElementExist
- I would also recommend to handle only
NoSuchElementException
rather than anyException
.- For example if the provided xpath is malformed or null the
FindElement
might throwArgumentException
(that's just an assumption, I don't know). You can handle that case differently
- For example if the provided xpath is malformed or null the
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;
}
}
-
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. butremainingSeconds
in your example is just a counter for number of attempts to recheck. as is .... if you setwaitUntilInSeconds
to 20, driver timeout is 30 seconds, then if we never find the control, we sit here 620 seconds \$\endgroup\$Michael Rieger– Michael Rieger2021年09月03日 16:37:41 +00:00Commented 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\$Peter Csala– Peter Csala2021年09月03日 17:27:03 +00:00Commented Sep 3, 2021 at 17:27
-
\$\begingroup\$ the closest thing for immediate results is
FindElements
which returns a collection. then check the count. \$\endgroup\$Michael Rieger– Michael Rieger2021年09月03日 17:29:48 +00:00Commented Sep 3, 2021 at 17:29
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.
Explore related questions
See similar questions with these tags.