Home > OS >  How to access the elements wrapped inside a ".lazyload-wrapper" using Selenium and C#? (Sc
How to access the elements wrapped inside a ".lazyload-wrapper" using Selenium and C#? (Sc

Time:06-02

I am building a testing tool for a popular hardware shop website in Australia. I want my test to go to a page that contains, let's say for example, 36 results (The page actually says "Showing 36 of 151 results") and click in the "add to cart" button in one randomly chosen product. To do this I am trying with driver.FindElements to capture all the 36 buttons in the page using a CssSelector.
The problem I am facing is that i can only get the first 12 items because the rest 24 are wrapped in a class=lazyload-wrapper and I just can't get the driver to capture them. I have tried scrolling the page to the bottom, I have tried inserting Thread.Sleep everywhere, I have tried with WebDriverWait, I have also recreated some deprecated functions from ExpectedConditions to make it work but I am still only getting 12 elements.
I have read pretty much all of the posts about similar issues here and in other forums but I am still stuck with the same 12 items. Perhaps Selenium is not the best choice to do this. If anyone can throw some light with on this challenge it will be greatly appreciated.
This is the class I am using to interact with the page, please forgive me for leaving all that commented out code, it's just to show all the failed attempts:

using System;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using System.Collections.ObjectModel;
using System.Linq;

namespace Model.Pages
{
    public class CarAccessoriesPage : BasePage
    {
        public CarAccessoriesPage(IWebDriver driver) : base(driver)
        {
        }

        /// <summary>
        /// Add a random product to the shopping cart.
        /// </summary>
        public CarAccessoriesPage AddRandomItemToCart()
        {
            // smooth scrolling down to force loading did not work
            // IJavaScriptExecutor jsex = (IJavaScriptExecutor)driver;
            // jsex.ExecuteScript("window.scrollTo({left:0, top:document.body.scrollHeight, behavior:'smooth'});");

            WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));

            // Scroll
            IJavaScriptExecutor jsex = (IJavaScriptExecutor)driver;
            jsex.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);");

            // maybe wait for the page to load? Did not work.
            wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
            
            // Maybe inserting an ugly pause... no bueno
            Thread.Sleep(3000);

            // driver.FindElement(By.CssSelector("[data-index='35']")); // this element is not found

            Random r = new Random();
            int i = r.Next(0, GetPartialResultsCount());

            // First approach throws OutOfRangeException because only 12 elements are found
            // IWebElement itemElement = new WebDriverWait(driver, TimeSpan.FromSeconds(5))
            //     .Until(d => d.FindElements(By.CssSelector("[data-locator='atcButton']")))[i];

            // Implementing a function from ExpectedConditions deprecated in C#, no good enough
            var elements = wait
                .Until(PresenceOfAllElementsLocatedBy(By.CssSelector("[data-locator='atcButton']")));

            // maybe trying access the article elements inside the wrapper... fail
            // var elements = driver.FindElements(By.CssSelector("article.lazyload-wrapper "));

            Console.WriteLine(elements.Count); // Always 12!!! I know there are better ways to log

            IWebElement ranItem = elements[i]; // Throws an OutOfRangeException if i > 11
            Thread.Sleep(1000);
            // center the element so the tob and bottom banners don intercept the click
            jsex.ExecuteScript("arguments[0].scrollIntoView({block: 'center'});", ranItem);
            Thread.Sleep(1000);
            ranItem.Click();

            Thread.Sleep(1000); // important or last function in test does not work (assertion fails: expected != actual)
            return this;
        }

        /// <summary>
        /// Get the number of items in the shopping cart.
        /// </summary>
        public int GetCartCount()
        {
            return Int32.Parse(driver.FindElement(By.ClassName("cartItemCount")).Text);
        }


        private Func<IWebDriver, ReadOnlyCollection<IWebElement>> PresenceOfAllElementsLocatedBy(By locator)
        {
            return (driver) =>
            {
                try
                {
                    var elements = driver.FindElements(locator);
                    return elements.Any() ? elements : null;
                }
                catch (StaleElementReferenceException)
                {
                    return null;
                }
            };
        }

        /// <summary>
        /// Generate a random int no larger than the Collection of products
        /// in the page. But due to lazy loading, the number generated was causing an out of range exception.
        /// </summary>
        private int GetPartialResultsCount()
        {
            string partialResultsCount = driver.FindElement(By.CssSelector(".resultsCount > p")).Text;
            return Int32.Parse(Regex.Match(partialResultsCount, @"\d ").Value);
        }

    }
}

The test itself is actually very straight forward:

using NUnit.Framework;
using Model.Pages;

namespace Tests
{
    public class ShoppingCartTests : BaseTests
    {

        [Test]
        public void ValidateCartItemsCount()
        {
            int cartCount = open<HomePage>()
                .GoToCarAccessoriesPage()
                .AddRandomItemToCart()
                .AddRandomItemToCart()
                .GetCartCount();

            // Validate cart has 2 items
            Assert.AreEqual(2, cartCount);
        }
    }
}

Finally I just probably need to mention that the test will run and work if I only consider 12 items, but it has become like a personal obsession to get all the items that a user could see in the page. Also, no sure if I should share the actual website I am testing or this goes against the community rules or best practice. Probably is enough to say that is the most popular hardware shop in Australia and the website is fairly complex. Thanks.

CodePudding user response:

Maybe find the wrapped elements using an xpath selector like

"//*[contains(@class, 'lazyload-wrapper')]//button[contains(@data-locator, 'atcButton')]"

And then add the result to the one you usually get?

CodePudding user response:

So, after many tries I found a way to make the test work and capture the elements wrapped in the lazyloader div. It's not very elegant and I am not exactly sure why do I need to scroll twice to the browser to actually perform the scrolling, and also I am not sure why the elements go missing if I remove one the pauses. By the way, the pauses I am using are simple Thread.Sleep(1000) wrapped in a helper class. Here is the code:

using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System.Collections.ObjectModel;
using System.Linq;
using System.Diagnostics;
using Model.Helpers;

namespace Model.Pages
{
    public class CarAccessoriesPage : BasePage
    {
        public CarAccessoriesPage(IWebDriver driver) : base(driver)
        {
        }

        /// <summary>
        /// Add a random product to the shopping cart.
        /// </summary>
        public CarAccessoriesPage AddRandomItemToCart()
        {
            WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
            IJavaScriptExecutor jsex = (IJavaScriptExecutor)driver;
            Random r = new Random();

            // Wait for the page to load.
            wait.Until(d => ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));

            // Scroll
            jsex.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);");

            // Slow down
            BrowserHelper.Pause();

            // Scroll again
            jsex.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);");

            // Brake
            BrowserHelper.Pause();

            // Get all the 'add to cart' button elements
            var elements = wait
                .Until(PresenceOfAllElementsLocatedBy(By.CssSelector("[data-locator='atcButton']")));

            // Test output
            Trace.WriteLine($"Total products found: {elements.Count}");

            // Select a random item
            IWebElement ranItem = elements[r.Next(0, elements.Count)];

            // Brake
            BrowserHelper.Pause();

            // Center the element so the top and bottom banners don't intercept the click
            jsex.ExecuteScript("arguments[0].scrollIntoView({block: 'center'});", ranItem);

            // Brake a little 
            BrowserHelper.Pause();

            // Click 'add to cart'
            ranItem.Click();

            // Brake again. Important or last function in test does not work (assertion fails: expected != actual)
            BrowserHelper.Pause(); 

            return this;
        }

        /// <summary>
        /// Get the number of items in the shopping cart.
        /// </summary>
        public int GetCartCount()
        {
            return Int32.Parse(driver.FindElement(By.ClassName("cartItemCount")).Text);
        }


        private Func<IWebDriver, ReadOnlyCollection<IWebElement>> PresenceOfAllElementsLocatedBy(By locator)
        {
            return (driver) =>
            {
                try
                {
                    var elements = driver.FindElements(locator);
                    return elements.Any() ? elements : null;
                }
                catch (StaleElementReferenceException)
                {
                    return null;
                }
            };
        }
    }
}
  • Related