Search code examples
javaselenium-webdriversafaridriver

FluentWait Not Working Properly: Youtube Example


So I had it working earlier but I messed up something in my code and now the FluentWait method doesnt seem to call properly. If I run it using quickRun set to false it works as intended (because of the implicit) but when I set it to true it doesnt as it will not wait for the elements to load correctly. Does anyone know exactly what I did wrong?

package myPackage;

import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.safari.SafariDriver;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import com.google.common.base.Function;

//import com.gargoylesoftware.htmlunit.javascript.host.Console;
//https://www.codeproject.com/articles/143430/test-your-web-application-s-ui-with-junit-and-sele

//this will open a dynamic page example (ie. youtube) trending
public class youtubeTest {

  public boolean quickRun = false; //Disable for debugging otherwise full speed
  private static int defaultDebugDelay = 2; //Time in sec for next test to occur in debug 

  //do no change any of the below
  private String testUrl; //target url destination ie youtube
  private WebDriver driver; //webdriver instance to reference within class
  private int testIndex = 1; //initial index value for console outputting

  public WebElement fluentWait(final By locator) {
    Wait < WebDriver > wait = new FluentWait < WebDriver > (driver)
      .withTimeout(30, TimeUnit.SECONDS)
      .pollingEvery(1, TimeUnit.SECONDS)
      .ignoring(NoSuchElementException.class);

    WebElement foo = wait.until(new Function < WebDriver, WebElement > () {
      public WebElement apply(WebDriver driver) {
        return driver.findElement(locator);
      }
    });

    return foo;
  };

  @
  Before
  public void beforeTest() {
    driver = new SafariDriver();
    System.out.println("Setting up Test...");
    if (quickRun) {
      System.out.println("Test Type: Quick Run (Fastest Mode)");
    } else {
      System.out.println("Test Type: Slow Run (Debug Mode) - Each Test has a " + defaultDebugDelay + " sec call time buffer");
    }
    testUrl = "https://www.youtube.com";
    driver.get(testUrl);
    System.out.println("Setting Driver " + driver + "for url: " + testUrl);

  }

  @
  Test
  public void Test() {
    //insert unit tests within here
    //open yt nav menu
    locateClickableElement("#appbar-guide-button");
    //go to trending
    locateClickableElement("#trending-guide-item");
    //click on 4th Trending video from list
    //locateClickableElement(".expanded-shelf-content-item-wrapper", 3);
    locateClickableElement(".expanded-shelf-content-item-wrapper");


  }

  @
  After
  public void afterTest() throws Exception {
    //wait 10 sec before closing test indefinitely
    System.out.println("Test auto ending in 10 seconds...");
    Thread.sleep(10000);
    stopTest();
  }

  //individual unit tests
  private void locateClickableElement(String ExpectedElement, int child) {
    //format string into something like: "ELEMENT:nth-child(1)"
    String formattedString = ExpectedElement + ":nth-child(" + child + ")";
    System.out.println("Strung: " + formattedString);
    locateClickableElement(formattedString);
  }

  private void locateClickableElement(String ExpectedElement) {
    try {
      System.out.println("Test " + testIndex + ": locateClickableElement(" + ExpectedElement + ")");

      //do absolute delay for visual debugging
      if (!quickRun) Thread.sleep(2000);

      //click on target if found
      fluentWait(By.cssSelector(ExpectedElement)).click();
      System.out.println("Test " + testIndex + ": Successful Click on Element(" + ExpectedElement + ")");

    } catch (Exception e) {
      //whenever error is found output it and end program
      System.out.println("Error Could not locateClickableElement(" + ExpectedElement + ")");
      System.out.println("Exception Handled:" + e.getMessage());
      stopTest("error");
    }
    testIndex++;
  }

  private void stopTest() {
    System.out.println("Test Completed: Reached End.");
    driver.quit();
  }

  private void stopTest(String typeError) {
    System.out.println("Test Completed: With an Error.");
    driver.quit();
  }

}


Solution

  • I would write this a different way and offer some advice.

    1. Don't slow your test down using "debug mode". If you want to debug your test, use breakpoints and step through the code to see how it's working.

    2. You don't need FluentWait here. A simple WebDriverWait using ExpectedConditions.elementToBeClickable(locator) will work just fine and is less complicated. You shouldn't even need it, if you accept my changes.

    3. Don't pass locators using String, use the intended locator class, By. You won't have to interpret it, translate it, etc. and it will be faster and more flexible.

    4. Unless you are trying to test the UI (which I'm assuming you don't work for youtube), then you can just navigate to the Trending page using the Trending link at the top of the page. It will save you time and clicks. If you aren't testing it, don't test it... get to where you are going as fast as possible. You don't want your test failing due to UI you aren't trying to test and you always want your tests to go as fast as possible. (NOTE: You could even navigate directly to the trending URL.)

    5. You don't need the locateClickableElement() functions. Just click the links... it should be a one liner. If there's an error, it will be obvious. You don't need to print, "There was an error." after an exception message was printed.

    6. You don't need the stopTest() functions... just stop the test. When the browser closes, it will be obvious the test is complete.

    The rewritten code is below. It's nice and simple (and short) and should be faster.

    public class youtubeTest
    {
        // do no change any of the below
        private String testUrl = "https://www.youtube.com"; // target url destination ie youtube
        private WebDriver driver; // webdriver instance to reference within class
    
        private By trendingGuideLinkLocator = By.cssSelector("#trending-guide-item");
        private By trendingLinkLocator = By.xpath("//h2[contains(.,'Trending')]");
    
        @Before
        public void beforeTest()
        {
            System.out.println("Setting up Test..."); // if you are going to have this statement, put it at the start of beforeTest()
            driver = new SafariDriver();
            driver.get(testUrl);
            System.out.println("Set Driver " + driver + "for url: " + testUrl);
        }
    
        @Test
        public void Test()
        {
            // insert unit tests within here
            driver.findElement(trendingLinkLocator).click(); // just click the Trending link, it's faster
            driver.findElements(trendingGuideLinkLocator).get(3).click();
        }
    
        @After
        public void afterTest()
        {
            driver.close();
            driver.quit();
        }
    }
    

    If you don't want to change all this, the simple answer to your question is replace FluentWait with WebDriverWait.

    fluentWait(By.cssSelector(ExpectedElement)).click();
    

    would be replaced by

    new WebDriverWait(driver, 10).until(ExpectedConditions.elementToBeClickable(trendingLinkLocator)).click();