avatarAlex Siminiuc

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

11190

Abstract

i>open the home page of the site</li><li>verify that the home page url is correct</li><li>search for a keyword</li><li>verify that the results page url is correct</li><li>verify that the total result count is greater than 0</li><li>verify that the results count per page is equal to 10</li></ul><p id="959d">The code has no synchronization implemented with the pages of the site.</p><p id="5ed2">If the site loads fast, both tests will work.</p><p id="6e34">But if the site loads slowly, the tests will start breaking.</p><p id="7d7a">To prevent breaking, we need to add synchronization in a few places:</p><ul><li>before finding an element (such as a texbox or button), the code should wait until the element is displayed and enabled</li><li>before finding an element such as a label, the code should wait until the element is displayed</li><li>before finding a list of elements, the code should wait until there is at least an element displayed</li><li>before interacting with a page, the code should wait until the url is correct</li></ul><p id="8ddc">This is the code updated with the needed synchonisation. It does not repeat the locators, test fixtures and the test methods as they have no changes:</p><div id="b706"><pre><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OpenHomePage</span>()</span> { driver.Navigate().GoToUrl(homeUrl); }</pre></div><div id="0ee4"><pre><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SearchFor</span>(<span class="hljs-params"><span class="hljs-built_in">string</span> keyword</span>)</span> {

<span class="hljs-comment">//create a 30 seconds timeout</span> TimeSpan timeout = TimeSpan.FromSeconds(<span class="hljs-number">30</span>);</pre></div><div id="1c4e"><pre> //create the <span class="hljs-built_in">wait</span> object using the driver and the <span class="hljs-built_in">timeout</span> WebDriverWait <span class="hljs-built_in">wait</span> = new WebDriverWait(driver, <span class="hljs-built_in">timeout</span>);</pre></div><div id="c396"><pre> //<span class="hljs-keyword">create</span> the condition <span class="hljs-keyword">for</span> <span class="hljs-keyword">search</span> <span class="hljs-type">box</span> <span class="hljs-keyword">to</span> be displayed <span class="hljs-keyword">and</span> enabled Func<IWebDriver, <span class="hljs-type">bool</span>> isSearchBoxEnabled = d => { IWebElement e = d.FindElement(searchBoxId); <span class="hljs-keyword">return</span> e.Displayed && e.Enabled; };</pre></div><div id="c9c9"><pre> //<span class="hljs-keyword">wait</span> <span class="hljs-keyword">until</span> the condition <span class="hljs-keyword">is</span> <span class="hljs-literal">true</span> <span class="hljs-keyword">wait</span>.<span class="hljs-keyword">Until</span>(isSearchBoxEnabled);</pre></div><div id="6a97"><pre> //find search box IWebElement searchBox <span class="hljs-operator">=</span> driver.FindElement(searchBoxId)<span class="hljs-comment">;</span> searchBox.SendKeys(keyword)<span class="hljs-comment">;</span></pre></div><div id="4e76"><pre> <span class="hljs-comment">//create the condition about search button being enabled</span> Func<IWebDriver, bool> isSearchButtonEnabled = <span class="hljs-function"><span class="hljs-params">d</span> =></span> { IWebElement e = d.FindElement(searchButtonId); <span class="hljs-keyword">return</span> e.Displayed && e.Enabled; };</pre></div><div id="969e"><pre> <span class="hljs-comment">//wait until the search button is displayed and enabled</span> wait<span class="hljs-selector-class">.Until</span>(isSearchButtonEnabled);</pre></div><div id="095a"><pre> //find search button IWebElement searchButton <span class="hljs-operator">=</span> driver.FindElement(searchButtonId)<span class="hljs-comment">;</span> searchButton.Click()<span class="hljs-comment">;</span></pre></div><div id="ec33"><pre>} </pre></div><div id="c95c"><pre><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-built_in">string</span> <span class="hljs-title">SearchTitle</span>()</span> {</pre></div><div id="4f88"><pre> //timeout can be created <span class="hljs-keyword">when</span> passed as <span class="hljs-keyword">parameter</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">wait</span>
WebDriverWait <span class="hljs-keyword">wait</span> = <span class="hljs-keyword">new</span> WebDriverWait(driver,
TimeSpan.FromSeconds(<span class="hljs-number">30</span>));</pre></div><div id="b0af"><pre> <span class="hljs-comment">//wait until search title is displayed</span> <span class="hljs-comment">//wait and condition can be used together in the same sentence</span> wait.Until(<span class="hljs-function"><span class="hljs-params">d</span> =></span> { IWebElement e = d.FindElement(searchTitleXpath); <span class="hljs-keyword">return</span> e.Displayed; });</pre></div><div id="aa17"><pre> //find search title IWebElement searchTitle <span class="hljs-operator">=</span> driver.FindElement(searchTitleXpath)<span class="hljs-comment">;</span></pre></div><div id="118d"><pre> <span class="hljs-keyword">return</span> searchTitle.<span class="hljs-built_in">Text</span>; }</pre></div><div id="358f"><pre><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-built_in">int</span> <span class="hljs-title">ResultCount</span>()</span> {</pre></div><div id="b74a"><pre> WebDriverWait wait = <span class="hljs-keyword">new</span> <span class="hljs-type">WebDriverWait</span>(driver,
TimeSpan.FromSeconds(<span class="hljs-number">30</span>));</pre></div><div id="e81f"><pre> //wait for the results count to be displayed wait.Until(d <span class="hljs-operator">=</span>> { IWebElement e <span class="hljs-operator">=</span> d.FindElement(resultCountXpath)<span class="hljs-comment">;</span> return e.Displayed<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="1eda"><pre> //find results count IWebElement countLabel <span class="hljs-operator">=</span> driver.FindElement(resultCountXpath)<span class="hljs-comment">;</span></pre></div><div id="6dd3"><pre> string countText <span class="hljs-operator">=</span> countLabel.Text<span class="hljs-comment">;</span></pre></div><div id="f7a1"><pre> <span class="hljs-built_in">int</span> i = countText.IndexOf(“of “) + <span class="hljs-number">3</span>; <span class="hljs-built_in">int</span> j = countText.IndexOf(“ results”);</pre></div><div id="6148"><pre> <span class="hljs-attr">countText</span> = countText.Substring(i, j — i)<span class="hljs-comment">;</span></pre></div><div id="f993"><pre> int count <span class="hljs-operator">=</span> Int32.Parse(countText)<span class="hljs-comment">;</span></pre></div><div id="3301"><pre><span class="hljs-built_in"> return</span> <span class="hljs-built_in">count</span>; }</pre></div><div id="9390"><pre><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-built_in">int</span> <span class="hljs-title">ResultCountPerPage</span>()</span> {</pre></div><div id="b138"><pre> WebDriverWait wait = <span class="hljs-keyword">new</span> <span class="hljs-type">WebDriverWait</span>(driver, TimeSpan.FromSeconds(<span class="hljs-number">30</span>));</pre></div><div id="cd7c"><pre> <span class="hljs-comment">// create the condition for at least one result being found</span> Func<IWebDriver, bool> areResultsFound = <span class="hljs-function"><span class="hljs-params">d</span> =></span> { ReadOnlyCollection<IWebElement> titles =
driver.FindElements(resultLinkXpath);</pre></div><div id="916e"><pre> return titles.Count > <span class="hljs-number">0</span><span class="hljs-comment">;</span> }<span class="hljs-comment">;</span></pre></div><div id="d327"><pre> //<span class="hljs-keyword">wait</span> <span class="hljs-keyword">until</span> the condition <span class="hljs-keyword">is</span> <span class="hljs-literal">true</span> <span class="hljs-keyword">wait</span>.<span class="hljs-keyword">Until</span>(areResultsFound);</pre></div><div id="64d1"><pre> ReadOnlyCollection<IWebElement> resultTitles <span class="hljs-operator">=</span>
driver.FindElements(resultLinkXpath)<span class="hljs-comment">;</span></pre></div><div id="9d47"><pre> <span class="hljs-keyword">return</span> resultTitles.<span class="hljs-built_in">Count</span>; }</pre></div><div id="22d3"><pre><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-built_in">string</span> <span class="hljs-title">Url</span>()</span> { <span class="hljs-keyword">return</span> driver.Url; }</pre></div><p id="0659">Let’s go into the details of how this waiting for a condition works.</p><p id="bc45">We can take as an example the waiting until the search box is displayed and enabled.</p><p id="a935">The waiting is implemented in 4 steps:</p><p id="8346">1. <b>create a 30 seconds timeout object:</b></p><div id="99c8"><pre>TimeSpan timeout <span class="hljs-operator">=</span> TimeSpan.FromSeconds(<span class="hljs-number">30</span>)<span class="hljs-comment">;</span></pre></div><p id="d2c6">2. <b>create a WebDriverWait object using the driver and the timeout as parameters:</b></p><div id="0c2d"><pre> WebDriverWait <span class="hljs-built_in">wait</span> = new WebDriverWait(driver, <span class="hljs-built_in">timeout</span>);</pre></div><p id="d7eb"><b>3. create a condition for an element:</b></p><p id="2e04">For example, the following condition checks if the search box is displayed and enabled:</p><div id="12a1"><pre>Func<IWebDriver, bool> isSearchBoxEnabled = <span class="hljs-function"><span class="hljs-params">d</span> =></span> { IWebElement e = d.FindElement(searchBoxId); <span class="hljs-keyword">return</span> e.Displayed && e.Enabled; };</pre></div><p id="130a">The condition finds the search box element using its id and returns</p><ul><li>true if the element is displayed and enabled</li><li>false if the element is not displayed or not enabled</li></ul><p id="ad8b">The condition needs to be executed so we will use the wait object for this.</p><p id="f42b"><b>4. The wait object executes the condition until the condition passes or until the condition fails and the timeout expires:</b></p><div id="9105"><pre> wait<span class="hljs-selector-class">.Until</span>(isSearchBoxEnabled); </pre></div><p id="1938">W

Options

hen the condition is executed by the wait object, the d variable used inside of the condition is substituted by the driver of the wait.</p><p id="c992">So,</p><div id="72cd"><pre>d => { IWebElement e = d.FindElement(searchBoxId); <span class="hljs-keyword">return</span> e.Displayed && e.Enabled; };</pre></div><p id="3233">becomes</p><div id="df9d"><pre>driver => { IWebElement e = driver.FindElement(searchBoxId); <span class="hljs-keyword">return</span> e.Displayed && e.Enabled; };</pre></div><p id="d9d5">This is how the waiting works:</p><p id="77bd">1. the wait executes the condition.</p><p id="48cd">2. if the condition returns true, the wait ends successfully.</p><p id="59e3">3. if the condition returns false, the wait is paused for 500ms; the wait then tries the condition again until either</p><ul><li>the condition is true or</li><li>the timeout of the wait is reached</li></ul><p id="dc14">4. if the condition fails and the timeout is reached, the code generates a timeout exception.</p><p id="a077">The code synchronizes now with the site.</p><p id="20d7">Before finding any element, it waits until the element is available.</p><p id="7045">It also waits until at least a title is available before finding all titles.</p><p id="a270">One thing that can be improved is to <b>re-use the wait object instead of creating this object in each method.</b></p><p id="3b77">We can create the wait as a class member and instantiate it in the SetUp() method:</p><div id="c317"><pre>//create the <span class="hljs-keyword">wait</span> as <span class="hljs-keyword">class</span> member so <span class="hljs-built_in">all</span> methods can share it <span class="hljs-keyword">private</span> WebDriverWait <span class="hljs-keyword">wait</span>;</pre></div><div id="7387"><pre>[<span class="hljs-meta">SetUp</span>] <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetUp</span>()</span> {</pre></div><div id="6455"><pre> driver <span class="hljs-operator">=</span> new ChromeDriver()<span class="hljs-comment">;</span> driver.Manage().Window.Maximize()<span class="hljs-comment">;</span> </pre></div><div id="e0f1"><pre> //instantiate the <span class="hljs-built_in">wait</span> TimeSpan <span class="hljs-built_in">timeout</span> = TimeSpan.FromSeconds(30); this.wait = new WebDriverWait(driver, <span class="hljs-built_in">timeout</span>);</pre></div><div id="1816"><pre> <span class="hljs-keyword">wait</span>.Message = “<span class="hljs-keyword">wait</span> timed <span class="hljs-keyword">out</span> <span class="hljs-keyword">after</span> <span class="hljs-number">30</span> seconds”; <span class="hljs-keyword">wait</span>.PollingInterval = TimeSpan.FromMilliseconds(<span class="hljs-number">250</span>); <span class="hljs-keyword">wait</span>.IgnoreExceptionTypes(typeof(NotFoundException));</pre></div><div id="005a"><pre>}</pre></div><p id="5266">Since the wait is a class member, all class methods can use it:</p><div id="a025"><pre>private string SearchTitle() { wait.Until(d <span class="hljs-operator">=</span>> { IWebElement e <span class="hljs-operator">=</span> d.FindElement(searchTitleXpath)<span class="hljs-comment">;</span> return e.Displayed<span class="hljs-comment">;</span> })<span class="hljs-comment">;</span></pre></div><div id="a8f1"><pre> IWebElement searchTitle <span class="hljs-operator">=</span> driver.FindElement(searchTitleXpath)<span class="hljs-comment">;</span> return searchTitle.Text<span class="hljs-comment">;</span></pre></div><div id="d2f6"><pre>}</pre></div><p id="acb8">In the SetUp() method, we customized the wait object by</p><ul><li><b>changing the polling interval</b>; the default retry interval is 500 ms so we reduced it to 250 ms</li><li>if a NotfoundException is generated by the condition, <b>this exception will be ignored</b></li><li><b>the message displayed by the wait if it times out is modified as well</b></li></ul><p id="c0da">The class’s methods share now the wait object.</p><p id="b042">But their code is so verbose.</p><p id="351e">We can reduce the verbosity by creating methods that handle the waiting for elements:</p><div id="6c18"><pre><span class="hljs-comment">//wait until the element matched by locator is displayed and enabled</span> <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-type">void</span> <span class="hljs-title">WaitUntilElementIsEnabled</span><span class="hljs-params">(By locator)</span> </span>{</pre></div><div id="7a6a"><pre> Func<IWebDriver, bool> condition = <span class="hljs-function"><span class="hljs-params">d</span> =></span> { IWebElement e = d.FindElement(locator); <span class="hljs-keyword">return</span> e.Displayed && e.Enabled; };</pre></div><div id="3e8c"><pre> wait<span class="hljs-selector-class">.Until</span>(condition);</pre></div><div id="77b8"><pre>} </pre></div><div id="36ea"><pre><span class="hljs-comment">//wait until the element matched by locator is displayed</span> <span class="hljs-comment">//the wait and condition are used together</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">WaitUntilElementIsDisplayed</span><span class="hljs-params">(By locator)</span> { wait.Until(d => { <span class="hljs-type">IWebElement</span> <span class="hljs-variable">e</span> <span class="hljs-operator">=</span> d.FindElement(locator); <span class="hljs-keyword">return</span> e.Displayed; }); } </pre></div><div id="804e"><pre><span class="hljs-comment">//wait until the current url includes a keyword</span> <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-built_in">bool</span> <span class="hljs-title">UrlContains</span>(<span class="hljs-params"><span class="hljs-built_in">string</span> keyword</span>)</span> { wait.Until(d => { <span class="hljs-keyword">return</span> d.Url.ToLower().Contains(keyword); });</pre></div><div id="6354"><pre> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; }</pre></div><p id="0bd8">With the new “wait” methods, the class’s methods become much simpler. For example,</p><div id="c551"><pre><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SearchFor</span>(<span class="hljs-params"><span class="hljs-built_in">string</span> keyword</span>)</span> {</pre></div><div id="7b72"><pre> <span class="hljs-built_in">WaitUntilElementIsEnabled</span>(searchBoxId);</pre></div><div id="9e8e"><pre> IWebElement searchBox <span class="hljs-operator">=</span> driver.FindElement(searchBoxId)<span class="hljs-comment">;</span> searchBox.SendKeys(keyword)<span class="hljs-comment">;</span></pre></div><div id="7229"><pre> <span class="hljs-built_in">WaitUntilElementIsEnabled</span>(searchButtonId);</pre></div><div id="eb66"><pre> IWebElement searchButton <span class="hljs-operator">=</span> driver.FindElement(searchButtonId)<span class="hljs-comment">;</span> searchButton.Click()<span class="hljs-comment">;</span></pre></div><div id="02f1"><pre>}</pre></div><p id="57ed">Notice that a method was added also for waiting until the current url includes a keyword.</p><p id="f913">Because of it, the tests can be improved as well:</p><div id="3884"><pre>[<span class="hljs-meta">Test</span>] <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Search_For_Keyword_Returns_Results</span>()</span> {</pre></div><div id="ea6c"><pre> <span class="hljs-built_in">OpenHomePage</span>(); Assert<span class="hljs-selector-class">.IsTrue</span>(UrlContains(“vpl.ca”), “home page homeUrl does not <span class="hljs-attribute">contain</span> vpl<span class="hljs-selector-class">.ca</span>!”);</pre></div><div id="afc5"><pre> <span class="hljs-built_in">SearchFor</span>(keyword); Assert<span class="hljs-selector-class">.IsTrue</span>(UrlContains(resultsUrl), “results page homeUrl is wrong!”);</pre></div><div id="c34f"><pre> Assert.IsTrue(ResultCount() > <span class="hljs-number">0</span>, “<span class="hljs-literal">result</span> <span class="hljs-built_in">count</span> <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> positive!”); Assert.AreEqual(<span class="hljs-number">10</span>, ResultCountPerPage(), “<span class="hljs-keyword">the</span> title <span class="hljs-built_in">count</span> <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">equal</span> <span class="hljs-keyword">to</span> <span class="hljs-number">10</span>!”);</pre></div><div id="df12"><pre>}</pre></div><p id="a7da">Finally, we could make another improvement to the code.</p><p id="40ba">We can create new methods that wait for an element before finding it:</p><div id="eb2b"><pre>private IWebElement <span class="hljs-built_in">FindEnabledElement</span>(By locator) { <span class="hljs-built_in">WaitUntilElementIsEnabled</span>(locator);</pre></div><div id="d20e"><pre> IWebElement element <span class="hljs-operator">=</span> driver.FindElement(locator)<span class="hljs-comment">;</span></pre></div><div id="af28"><pre> <span class="hljs-keyword">return</span> element; }</pre></div><div id="205d"><pre>private IWebElement <span class="hljs-built_in">FindElement</span>(By locator) { <span class="hljs-built_in">WaitUntilElementIsDisplayed</span>(locator);</pre></div><div id="cf9d"><pre> IWebElement element <span class="hljs-operator">=</span> driver.FindElement(locator)<span class="hljs-comment">;</span></pre></div><div id="4ee7"><pre> <span class="hljs-keyword">return</span> element; }</pre></div><p id="9df8">Using these new methods simplifies the page methods further:</p><div id="d4cb"><pre><span class="hljs-keyword">private</span> <span class="hljs-literal">void</span> SearchFor(<span class="hljs-built_in">string</span> <span class="hljs-built_in">keyword</span>) { IWebElement searchBox = FindEnabledElement(searchBoxId); searchBox.SendKeys(<span class="hljs-built_in">keyword</span>);</pre></div><div id="8dbc"><pre> IWebElement searchButton <span class="hljs-operator">=</span> FindEnabledElement(searchButtonId)<span class="hljs-comment">;</span> searchButton.Click()<span class="hljs-comment">;</span> }</pre></div><p id="931b">You can create similarly your own expected conditions for any cases that may be important in your project:</p><ul><li>wait until an attribute of a web element is created</li><li>wait until an element is hidden</li><li>wait until an attribute of a web element has a particular value</li><li>wait until the page title is equal to a value</li></ul><p id="1d49">I hope that you found this article useful and interesting.</p><p id="7160">If it was so, please clap for it. Thanks.</p></article></body>

C# Expected Conditions are Deprecated. So what?

Photo by Jon Tyson on Unsplash

My recent Selenium automation projects are built in C#.

It was a while since I did any coding in this language but, since C# was created from Java, I did not expect more than a few days of adjusting to the Selenium C# binding.

One of the surprises I had was the fact that the C# ExpectedConditions class is deprecated.

How was I supposed to do any synchronization between the site and the tests without it?

It turned out that synchronization was still achievable in just a few lines of code.

I am going to show you a quick Selenium solution without any synchronization so it is easy to understand where the synchronization comes into play.

The test class has 2 tests only, both about searching for a keyword:

using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using System;
using System.Collections.ObjectModel;
using OpenQA.Selenium.Support.UI;
namespace UnitTestPackage {
 
  public class TestClass {
    private ChromeDriver driver;
    private readonly String homeUrl = “http://www.vpl.ca";
    private readonly String resultsUrl = “vpl.bibliocommons.com”;
    private readonly String keyword = “Java”;
    private readonly By searchBoxId = By.Id(“edit-search”);
    private readonly By searchButtonId = By.Id(“edit-submit”);
    private readonly By searchTitleXpath = 
       By.XPath(“//h1[@data-test-id = ‘searchTitle’]”);
    private readonly By resultCountXpath = 
       By.XPath(“(//span[@data-key = ‘pagination-text’])[1]”);
    private readonly By resultLinkXpath = 
       By.XPath(“//a[@data-key = ‘bib-title’]”);
    
    [SetUp]
    public void SetUp() {
      driver = new ChromeDriver();
      driver.Manage().Window.Maximize();
    }
    [TearDown]
    public void TearDown() {
      driver.Quit();
    }
    [Test]
    public void Search_For_Java_Works() {
      OpenHomePage();
 
      Assert.IsTrue(Url().Contains(“vpl.ca”), 
                    “home page homeUrl does not contain vpl.ca!”);
 
 
      SearchFor(keyword);
 
      Assert.IsTrue(Url().Contains(resultsUrl), 
                    “results page homeUrl is wrong!”);
      Assert.IsTrue(SearchTitle().Contains(keyword), 
                 “Java is not included in the search page title”);
    }
 
    [Test]
    public void Search_For_Keyword_Returns_Results() {
      OpenHomePage();
   
      Assert.IsTrue(Url().Contains(“vpl.ca”), 
                 “home page homeUrl does not contain vpl.ca!”);
   
      SearchFor(keyword);
   
      Assert.IsTrue(Url().Contains(resultsUrl), 
                    “results page homeUrl is wrong!”);
      Assert.IsTrue(ResultCount() > 0, 
                    “result count is not positive!”);
   
      Assert.AreEqual(10, ResultCountPerPage(), 
                      “the title count is not equal to 10!”);
   }
 
   private void OpenHomePage() {
     driver.Navigate().GoToUrl(homeUrl);
   }
 
   private void SearchFor(string keyword) {
 
     IWebElement searchBox = driver.FindElement(searchBoxId);
     searchBox.SendKeys(keyword);
     IWebElement searchButton = driver.FindElement(searchButtonId);
     searchButton.Click();
   }
   private string SearchTitle(){
 
     IWebElement searchTitle = driver.FindElement(searchTitleXpath);       
     return searchTitle.Text;
   }
   private int ResultCount() {
 
     IWebElement countLabel = driver.FindElement(resultCountXpath);
     string countText = countLabel.Text;
     int i = countText.IndexOf(“of “) + 3;
     int j = countText.IndexOf(“ results”);
     countText = countText.Substring(i, j — i);
     int count = Int32.Parse(countText);
     return count;
  }
  private int ResultCountPerPage() {
    ReadOnlyCollection<IWebElement> titles =  
                driver.FindElements(resultLinkXpath);
    return titles.Count;
  }
  private string Url() {
    return driver.Url;
  }
 }
}

The code should be easy to understand.

The first test has the following steps:

  • open the home page of the site
  • verify that the home page url is correct
  • search for a keyword
  • verify that the results page url is correct
  • verify that the search keyword is included in the results page header

The second test has similar steps:

  • open the home page of the site
  • verify that the home page url is correct
  • search for a keyword
  • verify that the results page url is correct
  • verify that the total result count is greater than 0
  • verify that the results count per page is equal to 10

The code has no synchronization implemented with the pages of the site.

If the site loads fast, both tests will work.

But if the site loads slowly, the tests will start breaking.

To prevent breaking, we need to add synchronization in a few places:

  • before finding an element (such as a texbox or button), the code should wait until the element is displayed and enabled
  • before finding an element such as a label, the code should wait until the element is displayed
  • before finding a list of elements, the code should wait until there is at least an element displayed
  • before interacting with a page, the code should wait until the url is correct

This is the code updated with the needed synchonisation. It does not repeat the locators, test fixtures and the test methods as they have no changes:

private void OpenHomePage() {
  driver.Navigate().GoToUrl(homeUrl);
}
private void SearchFor(string keyword) {
 
  //create a 30 seconds timeout
  TimeSpan timeout = TimeSpan.FromSeconds(30);
  //create the wait object using the driver and the timeout
  WebDriverWait wait = new WebDriverWait(driver, timeout);
  //create the condition for search box to be displayed and enabled
  Func<IWebDriver, bool> isSearchBoxEnabled = 
        d => {
               IWebElement e = d.FindElement(searchBoxId);
               return e.Displayed && e.Enabled;
             };
  //wait until the condition is true
  wait.Until(isSearchBoxEnabled);
  //find search box
  IWebElement searchBox = driver.FindElement(searchBoxId);
  searchBox.SendKeys(keyword);
  //create the condition about search button being enabled
  Func<IWebDriver, bool> isSearchButtonEnabled =
        d =>
             {
               IWebElement e = d.FindElement(searchButtonId);
               return e.Displayed && e.Enabled;
             };
  //wait until the search button is displayed and enabled
  wait.Until(isSearchButtonEnabled);
  //find search button
  IWebElement searchButton = driver.FindElement(searchButtonId);
  searchButton.Click();
}
private string SearchTitle() {
  //timeout can be created when passed as parameter to wait  
  WebDriverWait wait = new WebDriverWait(driver,  
                                 TimeSpan.FromSeconds(30));
  //wait until search title is displayed
  //wait and condition can be used together in the same sentence
  wait.Until(d =>
                 {
                   IWebElement e = d.FindElement(searchTitleXpath);
                   return e.Displayed;
                 });
  //find search title
  IWebElement searchTitle = driver.FindElement(searchTitleXpath);
  return searchTitle.Text;
}
private int ResultCount() {
  WebDriverWait wait = new WebDriverWait(driver,  
                                   TimeSpan.FromSeconds(30));
  //wait for the results count to be displayed
  wait.Until(d =>
                 {
                    IWebElement e = d.FindElement(resultCountXpath);
                    return e.Displayed;
                 });
  //find results count
  IWebElement countLabel = driver.FindElement(resultCountXpath);
  string countText = countLabel.Text;
  int i = countText.IndexOf(“of “) + 3;
  int j = countText.IndexOf(“ results”);
  countText = countText.Substring(i, j — i);
  int count = Int32.Parse(countText);
  return count;
}
private int ResultCountPerPage() {
  WebDriverWait wait = new WebDriverWait(driver, 
                              TimeSpan.FromSeconds(30));
  // create the condition for at least one result being found
  Func<IWebDriver, bool> areResultsFound =
               d => {
                  ReadOnlyCollection<IWebElement> titles =     
                       driver.FindElements(resultLinkXpath);
                  return titles.Count > 0;
               };
  //wait until the condition is true
  wait.Until(areResultsFound);
  ReadOnlyCollection<IWebElement> resultTitles =         
                      driver.FindElements(resultLinkXpath);
  return resultTitles.Count;
}
private string Url() {
  return driver.Url;
}

Let’s go into the details of how this waiting for a condition works.

We can take as an example the waiting until the search box is displayed and enabled.

The waiting is implemented in 4 steps:

1. create a 30 seconds timeout object:

TimeSpan timeout = TimeSpan.FromSeconds(30);

2. create a WebDriverWait object using the driver and the timeout as parameters:

 WebDriverWait wait = new WebDriverWait(driver, timeout);

3. create a condition for an element:

For example, the following condition checks if the search box is displayed and enabled:

Func<IWebDriver, bool> isSearchBoxEnabled = 
            d => {
                   IWebElement e = d.FindElement(searchBoxId);
                   return e.Displayed && e.Enabled;
                 };

The condition finds the search box element using its id and returns

  • true if the element is displayed and enabled
  • false if the element is not displayed or not enabled

The condition needs to be executed so we will use the wait object for this.

4. The wait object executes the condition until the condition passes or until the condition fails and the timeout expires:

 wait.Until(isSearchBoxEnabled);

When the condition is executed by the wait object, the d variable used inside of the condition is substituted by the driver of the wait.

So,

d => {
       IWebElement e = d.FindElement(searchBoxId);
       return e.Displayed && e.Enabled;
};

becomes

driver => {
       IWebElement e = driver.FindElement(searchBoxId);
       return e.Displayed && e.Enabled;
 };

This is how the waiting works:

1. the wait executes the condition.

2. if the condition returns true, the wait ends successfully.

3. if the condition returns false, the wait is paused for 500ms; the wait then tries the condition again until either

  • the condition is true or
  • the timeout of the wait is reached

4. if the condition fails and the timeout is reached, the code generates a timeout exception.

The code synchronizes now with the site.

Before finding any element, it waits until the element is available.

It also waits until at least a title is available before finding all titles.

One thing that can be improved is to re-use the wait object instead of creating this object in each method.

We can create the wait as a class member and instantiate it in the SetUp() method:

//create the wait as class member so all methods can share it
private WebDriverWait wait;
[SetUp]
public void SetUp()
{
  driver = new ChromeDriver();
  driver.Manage().Window.Maximize();
  //instantiate the wait
  TimeSpan timeout = TimeSpan.FromSeconds(30);
  this.wait = new WebDriverWait(driver, timeout);
  wait.Message = “wait timed out after 30 seconds”;
  wait.PollingInterval = TimeSpan.FromMilliseconds(250);
  wait.IgnoreExceptionTypes(typeof(NotFoundException));
}

Since the wait is a class member, all class methods can use it:

private string SearchTitle()
{
  wait.Until(d =>
  {
     IWebElement e = d.FindElement(searchTitleXpath);
     return e.Displayed;
  });
  IWebElement searchTitle = driver.FindElement(searchTitleXpath);
  return searchTitle.Text;
}

In the SetUp() method, we customized the wait object by

  • changing the polling interval; the default retry interval is 500 ms so we reduced it to 250 ms
  • if a NotfoundException is generated by the condition, this exception will be ignored
  • the message displayed by the wait if it times out is modified as well

The class’s methods share now the wait object.

But their code is so verbose.

We can reduce the verbosity by creating methods that handle the waiting for elements:

//wait until the element matched by locator is displayed and enabled
private void WaitUntilElementIsEnabled(By locator)
{
  Func<IWebDriver, bool> condition =
       d => 
       {
           IWebElement e = d.FindElement(locator);
           return e.Displayed && e.Enabled;
       };
  wait.Until(condition);
}
//wait until the element matched by locator is displayed
//the wait and condition are used together
private void WaitUntilElementIsDisplayed(By locator)
{
  wait.Until(d =>
      {
         IWebElement e = d.FindElement(locator);
         return e.Displayed;
      });
}
//wait until the current url includes a keyword
private bool UrlContains(string keyword)
{
  wait.Until(d =>
        {
           return d.Url.ToLower().Contains(keyword);
        });
  return true;
}

With the new “wait” methods, the class’s methods become much simpler. For example,

private void SearchFor(string keyword)
{
  WaitUntilElementIsEnabled(searchBoxId);
  IWebElement searchBox = driver.FindElement(searchBoxId);
  searchBox.SendKeys(keyword);
  WaitUntilElementIsEnabled(searchButtonId);
  IWebElement searchButton = driver.FindElement(searchButtonId);
  searchButton.Click();
}

Notice that a method was added also for waiting until the current url includes a keyword.

Because of it, the tests can be improved as well:

[Test]
public void Search_For_Keyword_Returns_Results()
{
  OpenHomePage();
  Assert.IsTrue(UrlContains(“vpl.ca”), 
                “home page homeUrl does not contain vpl.ca!”);
  SearchFor(keyword);
  Assert.IsTrue(UrlContains(resultsUrl), 
                “results page homeUrl is wrong!”);
  Assert.IsTrue(ResultCount() > 0, “result count is not positive!”);
  Assert.AreEqual(10, ResultCountPerPage(), 
                  “the title count is not equal to 10!”);
}

Finally, we could make another improvement to the code.

We can create new methods that wait for an element before finding it:

private IWebElement FindEnabledElement(By locator)
{
  WaitUntilElementIsEnabled(locator);
  IWebElement element = driver.FindElement(locator);
  return element;
}
private IWebElement FindElement(By locator)
{
  WaitUntilElementIsDisplayed(locator);
  IWebElement element = driver.FindElement(locator);
  return element;
}

Using these new methods simplifies the page methods further:

private void SearchFor(string keyword)
{
  IWebElement searchBox = FindEnabledElement(searchBoxId);
  searchBox.SendKeys(keyword);
  IWebElement searchButton = FindEnabledElement(searchButtonId);
  searchButton.Click();
}

You can create similarly your own expected conditions for any cases that may be important in your project:

  • wait until an attribute of a web element is created
  • wait until an element is hidden
  • wait until an attribute of a web element has a particular value
  • wait until the page title is equal to a value

I hope that you found this article useful and interesting.

If it was so, please clap for it. Thanks.

Selenium
Selenium Webdriver
Test Automation
Testing
Recommended from ReadMedium