Search code examples
selenium-webdrivercss-selectorsselenium-chromedriverselenium-grid

Facing Exception in thread "main" org.openqa.selenium.NoSuchElementException: no such element: Unable to locate element:


My task

URL: https://www.homedepot.com/

Steps:

  1. All Departments is first mouseover
  2. Heating & Cooling is second mouseover
  3. Air Conditioners is third mouseover
  4. Click Portable Air Conditioners

I can do Steps 1 and 2 but I'm not able to find the WebElements for Step 3 and Step 4.

My code

ChromeOptions options = new ChromeOptions();
options.addArguments("--remote-allow-origins=*");
options.addArguments("--disable-notifications");
WebDriver driver = new ChromeDriver(options);
driver.get("https://www.homedepot.com/");
WebElement First = driver.findElement(By.xpath("(//a[@data-id='departmentsFlyout'])[1]"));
WebElement Second = driver.findElement(By.xpath("(//a[@data-level='Heating & Cooling'])[1]"));
// cannot able to find the below Third and Fourth WebElements//
WebElement Third = driver.findElement(By.xpath("//a[@title='Air Conditioners']"));
WebElement Fourth = driver.findElement(By.xpath("//a[@href='//www.homedepot.com/b/Heating-Venting-Cooling-Air-Conditioners-Coolers-Air-Conditioners-Portable-Air-Conditioners/N-5yc1vZc4m4']"));

Actions act = new Actions(driver);
act.moveToElement(First).build().perform();
act.moveToElement(Second).build().perform();
act.moveToElement(Third).build().perform();
act.moveToElement(Fourth).click().build().perform();

Solution

  • The main problem is that elements 3 & 4 don't exist until you open the navigation panels that create them. That's why you can't find 3 & 4 at the start.

    Another problem is that the final click gets intercepted by another element until the navigation menu is completely open. This takes some special handling past just waiting for clickable.

    Suggestions for improvement:

    1. You are overcomplicating your locators. (//a[@data-id='departmentsFlyout'])[1] could just as easily be //a[@data-id='departmentsFlyout'] and still work. Or better yet, make it a CSS selector a[data-id='departmentsFlyout']. CSS selector syntax is shorter and easier to read, it performs faster, and is better supported by a wide range of browsers. XPath has it's place but in my experience, my locator order of preference is 1. ID, 2. CSS selector, and then 3. XPath, when required.

    2. You don't need to .build() actions, you can just .perform() them, e.g.

      act.moveToElement(First).perform();
      
    3. Your locators are inconsistent. They still work but noticing and using the consistency can make your job a lot easier. The three last locators all can have the form, //a[@title='<Category>'] or for a CSS selector a[title='<Category>'].

    4. The final click doesn't need to be an Action, just make it a regular click. In my experience, Actions don't behave consistently so I avoid using them unless absolutely necessary.

    I added a click() support method that takes care of waiting for clickable and also handles ElementClickInterceptedException and StaleElementReferenceException.

    To make sure each step works, we chain the .findElement() then the .moveToElement() for each element, e.g.

    WebElement First = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("a[data-id='departmentsFlyout']")));
    act.moveToElement(First).perform();
    

    You'll notice that the first, second, and third elements are handled exactly the same except for the locator... wait, hover, wait, hover, wait, hover. They are prime candidates for refactoring into a hover() method that handles waiting for visible and then action to hover.

    The final working code and support methods look like

    import java.time.Duration;
    import java.time.LocalDateTime;
    import org.openqa.selenium.By;
    import org.openqa.selenium.ElementClickInterceptedException;
    import org.openqa.selenium.StaleElementReferenceException;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.chrome.ChromeOptions;
    import org.openqa.selenium.interactions.Actions;
    import org.openqa.selenium.support.ui.ExpectedConditions;
    import org.openqa.selenium.support.ui.WebDriverWait;
    
    public class Ques11 {
        static WebDriver driver;
    
        public static void main(String[] args) {
            ChromeOptions options = new ChromeOptions();
            options.addArguments("--remote-allow-origins=*");
            options.addArguments("--disable-notifications");
            driver = new ChromeDriver(options);
            driver.manage().window().maximize();
            driver.get("https://www.homedepot.com/");
    
            hover(By.cssSelector("a[data-id='departmentsFlyout']"), 10);
            hover(By.cssSelector("a[title='Heating & Cooling']"), 10);
            hover(By.cssSelector("a[title='Air Conditioners']"), 10);
            click(By.cssSelector("a[title='Portable Air Conditioners']"), 10);
        }
    
        public static void click(By locator, int timeOutInSeconds) {
            LocalDateTime now = LocalDateTime.now();
            while (LocalDateTime.now().isBefore(now.plusSeconds(timeOutInSeconds))) {
                try {
                    new WebDriverWait(driver, Duration.ofSeconds(timeOutInSeconds)).until(ExpectedConditions.elementToBeClickable(locator)).click();
    
                    return;
                } catch (ElementClickInterceptedException | StaleElementReferenceException e) {
                    // do nothing, loop again
                }
            }
        }
    
        public static void hover(By locator, int timeOutInSeconds) {
            WebElement e = new WebDriverWait(driver, Duration.ofSeconds(timeOutInSeconds)).until(ExpectedConditions.visibilityOfElementLocated(locator));
            new Actions(driver).moveToElement(e).perform();
        }
    }