Strona główna > Waity w Selenium – implementacja Implicit oraz Explicit Wait (Java + C#)

Waity w Selenium – implementacja Implicit oraz Explicit Wait (Java + C#)

Niestabilne testy to koszmar senny niejednego testera. W Jenkinsie czerwono, wchodzisz na stronę, klikasz po aplikacji – działa. A przecież wyraźnie było, że nie znalazł elementu więc o co chodzi. Nierzadko chodzi o brak metod czekających (Implicit lub Explicit Wait) na pojawienie się jakiegoś elementu. Często też metody są, ale czekają niekoniecznie na to, na co powinny.

Jakie opcje w tym obszarze daje nam Selenium?

Implicit Wait

Implicit Wait jest prostszy w użyciu, bo wystarczy, że zdefiniujemy go raz dla WebDrivera.

//Java 
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
//C#
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);

Od tej pory każde wywołanie metody findElement(By by), będzie miało zaszyty timeout na wypadek, gdyby element nie był widoczny dla WebDrivera od razu. Oznacza to, że gdy WebDriver nie znajdzie takiego elementu, będzie odpytywał DOM przez 10 sekund aż go znajdzie. Jeżeli nie znajdzie go w ciągu zadanego czasu, sypnie wyjątkiem NoSuchElementException a więc poinformuje, że szukany element nie został odnaleziony na stronie.

I jeszcze dodam prosty przykład bardzo krótkiego testu implementującego Implicit Wait (tylko w Javie, całość wygląda podobnie w C#).

//Java+JUnit
public class Waits {
    WebDriver driver;
    String emailAddress = "jurijgagarin@gmail.com";
    String gmailURL = "https://www.gmail.com";
    @Before
    public void setUpDriver(){
        System.setProperty("webdriver.chrome.driver", "src/main/resources/chromedriver.exe");
        driver = new ChromeDriver();
    }
    @After
    public void quitDriver(){
        driver.close();
        driver.quit();
    }
    @Test
    public void implicitWait(){
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        driver.navigate().to(gmailURL);
        WebElement emailField = driver.findElement(By.cssSelector("#identifierId"));
        emailField.clear();
        emailField.sendKeys(emailAddress);
        WebElement nextButton = driver.findElement(By.cssSelector("#identifierNext"));
        nextButton.click();
        WebElement profileIdentifier = driver.findElement(By.cssSelector("#profileIdentifier"));
    }
}

Niewątpliwą zaletą tego rozwiązania jest szybkość implementacji – wystarczy jedna linijka kodu i od tej pory każde zapytanie o element będzie objętę tym timeoutem. Implicit Wait ma naturalnie także wady: obejmuje tylko metodę findElement(By by). Za tym idzie szereg przykładów, kiedy ta metoda będzie niewystarczająca.

Wyobraź sobie taki scenariusz: po przeładowaniu strony, np. po kliknięciu w przycisk logowania, potrzebujesz poczekać na jakiś element, który ładuje się na dwa razy. Co mam na myśli? Zostańmy przy przykładzie logowania. Po poprawnym logowaniu w prawym górnym rogu ekranu oczekujemy elementu zawierającego nazwę użytkownika, ale najpierw załaduje się element jeszcze bez nazwy i dopiero po chwili doładuje sobie nazwę użytkownika. A więc element będzie dostępny od razu ale nie w takim stanie w jakim go potrzebujemy. Implicit Wait z tym sobie nie poradzi.

Selenium Webdriver oprócz metod opierających się na szukaniu elementu i wykonywaniu na nim jakiejś akcji, dostępne ma także metody np. do obsługi ciasteczek. Użycie Implicit Waita nie zadziała na te metody – będziemy musieli użyć metody findElement(By by) na jakimś elemencie wcześniej by w trochę sztuczny sposób wymusić waita.

W takich sytacjach lepiej użyć Explicit Wait.

Explicit Wait

Explicit Wait ma już zupełnie inną implementację: musimy utworzyć obiekt klasy WebDriverWait przekazując mu WebDrivera oraz wartość timeoutu, a następnie wywołać na niej metodę until, w której podajemy warunek jaki musi być spełniony.

//Java
WebDriverWait wait = new WebDriverWait(driver, 5);
wait.until(warunek);
//C#
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
wait.Until(warunek);

Selenium w Javie dostarczane jest wraz z klasą ExpectedConditions, która zawiera predefiniowane metody z warunkami. Metod tych jest ogrom, pełna dokumentacja dostępna jest tutaj. Poniżej możecie zobaczyć przykład użycia Explicit Wait wraz z ExpectedConditions.

public class Waits {
    WebDriver driver;
    String emailAddress = "jurijgagarin@gmail.com";
    String gmailURL = "https://www.gmail.com";
    @Before
    public void setUpDriver(){
        System.setProperty("webdriver.chrome.driver", "src/main/resources/chromedriver.exe");
        driver = new ChromeDriver();
    }
    @After
    public void quitDriver(){
        driver.close();
        driver.quit();
    }
    @Test
    public void explicitWait(){
        WebDriverWait wait = new WebDriverWait(driver, 5);
        driver.navigate().to(gmailURL);
        wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#identifierId")));
        WebElement emailField = driver.findElement(By.cssSelector("#identifierId"));
        emailField.clear();
        emailField.sendKeys(emailAddress);
        wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#identifierNext")));
        WebElement nextButton = driver.findElement(By.cssSelector("#identifierNext"));
        nextButton.click();
        try {
            wait.until(ExpectedConditions.textToBe(By.cssSelector("#profileIdentifier"), emailAddress));
        } catch (TimeoutException e){
            throw new AssertionFailedError("The email address displayed was different than expected. " + e.getMessage());
        }
    }
}

Na powyższym przykładzie WebDriver dwa razy czeka aż element się wyświetli przed kliknięciem, a na końcu czeka, aż element “#profileIdentifier” będzie miał określony tekst. Jeżeli się tak nie stanie w ciągu 5 sekund, rzuci błędem asercji. Zauważ, że w przeciwieństwie do Implicit Wait, który w przypadku niedoczekania się na element w jakimś określonym czasie rzuca NoSuchElementException, Explicit Wait rzuca TimeoutException. Ta informacja jest istotna na przykład w przypadku obsługi wyjątków, jak w teście powyżej.

Mała uwaga dla osób działających w C# – ExpectedConditions zostały wycięte z Selenium i nie będą już wspierane. Wciąż jest dostępna paczka nugetowa je zawierająca: DotNetSeleniumExtras.WaitHelpers. W alternatywie można też użyć wyrażeń lambda i implementować Explicit Wait w poniższy sposób.

//C#
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
wait.Until(d => driver.FindElement(By.CssSelector("#product")).Displayed);

Co wybrać?

Społeczność Selenium zaleca wybór Explicit Wait nad Implicit Wait, ze względu na większą kontrolę i mniejsze ryzyko wydłużania się wykonań testów. Umiem sobie jednak wyobrazić sytuację, w których sama użyłabym Implicit Wait. Ponieważ ich implementacja jest prostsza i szybsza zastosowałabym takie rozwiązanie w krótkich i mało skomplikowanych projektach, gdzie nie byłoby ryzyka, że testy się niebezpiecznie wydłużą i gdzie nie będzie mi potrzebna aż taka kontrola nad metodami czekającymi. W praktyce nigdy mi się nie zdarzyło użyć, ale jak to mówią, wszystko jest dla ludzi. Oprócz Thread.Sleep(). To nie jest dla ludzi.