1. 程式人生 > 其它 >《手把手教你》系列技巧篇(二十五)-java+ selenium自動化測試-FluentWait(詳細教程)

《手把手教你》系列技巧篇(二十五)-java+ selenium自動化測試-FluentWait(詳細教程)

1.簡介

其實今天介紹也講解的也是一種等待的方法,有些童鞋或者小夥伴們會問巨集哥,這也是一種等待方法,為什麼不在上一篇文章中竹筒倒豆子一股腦的全部說完,反而又在這裡單獨寫了一篇。那是因為這個比較重要,所以巨集哥專門為她量身定製了一篇。

FluentWait是Selenium中功能強大的一種等待方式,翻譯成中文是流暢等待的意思。在介紹FluentWait之前,我們來討論下為什麼需要設定等待,我們前面介紹了隱式等待和顯式等待。在現在很多軟體產品為了加強前端的效果,採取了大量的AJAX 和Jquery技術,很多窗體內的資料,需要等待一會,才能載入完資料,才能出現一些元素,driver才能操作這些元素做一些事情。還有就是我們做一些操作,本身可能也需要等待一會才有資料顯示。所以在自動化指令碼開發過程,合理的設定時間等待是非常必要的,可以說百分之90以上的自動化測試用例執行失敗,基本上是很時間等待有關係,造成元素沒有及時在介面上顯示,而報no such element子類的錯誤。

2.FluentWait的定義

簡單來說,FluentWait就是一個普通的類,我們使用這個類能支援一直等待直到特定的條件出現。

1)是一個類而且是包org.openqa.selenium.support.ui的一部分

2)是Wait介面的一種實現

3)每個Fluent wait,我們可以設定等待最大時間,而且可以做設定等待的頻率去檢查一些特定的條件。

FluentWait 和 Explicit Wait的區別:簡單來說就是Explicit Wait裡有一些設定好了的前置條件的等待方式,而Fluent wait你可以設定自己的方法去處理各種等待的問題。

3.核心程式碼

3.1原始碼

巨集哥先看一下FluentWait的原始碼,如何檢視巨集哥這裡就不做贅述了。原始碼如下:

// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.support.ui;

import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;

import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.internal.Require;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * An implementation of the {@link Wait} interface that may have its timeout and polling interval
 * configured on the fly.
 *
 * <p>
 * Each FluentWait instance defines the maximum amount of time to wait for a condition, as well as
 * the frequency with which to check the condition. Furthermore, the user may configure the wait to
 * ignore specific types of exceptions whilst waiting, such as
 * {@link org.openqa.selenium.NoSuchElementException NoSuchElementExceptions} when searching for an
 * element on the page.
 *
 * <p>
 * Sample usage: <pre>
 *   // Waiting 30 seconds for an element to be present on the page, checking
 *   // for its presence once every 5 seconds.
 *   Wait&lt;WebDriver&gt; wait = new FluentWait&lt;WebDriver&gt;(driver)
 *       .withTimeout(30, SECONDS)
 *       .pollingEvery(5, SECONDS)
 *       .ignoring(NoSuchElementException.class);
 *
 *   WebElement foo = wait.until(new Function&lt;WebDriver, WebElement&gt;() {
 *     public WebElement apply(WebDriver driver) {
 *       return driver.findElement(By.id("foo"));
 *     }
 *   });
 * </pre>
 *
 * <p>
 * <em>This class makes no thread safety guarantees.</em>
 *
 * @param <T> The input type for each condition used with this instance.
 */
public class FluentWait<T> implements Wait<T> {

  protected static final long DEFAULT_SLEEP_TIMEOUT = 500;

  private static final Duration DEFAULT_WAIT_DURATION = Duration.ofMillis(DEFAULT_SLEEP_TIMEOUT);

  private final T input;
  private final java.time.Clock clock;
  private final Sleeper sleeper;

  private Duration timeout = DEFAULT_WAIT_DURATION;
  private Duration interval = DEFAULT_WAIT_DURATION;
  private Supplier<String> messageSupplier = () -> null;

  private List<Class<? extends Throwable>> ignoredExceptions = new ArrayList<>();

  /**
   * @param input The input value to pass to the evaluated conditions.
   */
  public FluentWait(T input) {
    this(input, Clock.systemDefaultZone(), Sleeper.SYSTEM_SLEEPER);
  }

  /**
   * @param input   The input value to pass to the evaluated conditions.
   * @param clock   The clock to use when measuring the timeout.
   * @param sleeper Used to put the thread to sleep between evaluation loops.
   */
  public FluentWait(T input, java.time.Clock clock, Sleeper sleeper) {
    this.input = Require.nonNull("Input", input);
    this.clock = Require.nonNull("Clock", clock);
    this.sleeper = Require.nonNull("Sleeper", sleeper);
  }

  /**
   * Sets how long to wait for the evaluated condition to be true. The default timeout is
   * {@link #DEFAULT_WAIT_DURATION}.
   *
   * @param timeout The timeout duration.
   * @return A self reference.
   */
  public FluentWait<T> withTimeout(Duration timeout) {
    this.timeout = timeout;
    return this;
  }

  /**
   * Sets the message to be displayed when time expires.
   *
   * @param message to be appended to default.
   * @return A self reference.
   */
  public FluentWait<T> withMessage(final String message) {
    this.messageSupplier = () -> message;
    return this;
  }

  /**
   * Sets the message to be evaluated and displayed when time expires.
   *
   * @param messageSupplier to be evaluated on failure and appended to default.
   * @return A self reference.
   */
  public FluentWait<T> withMessage(Supplier<String> messageSupplier) {
    this.messageSupplier = messageSupplier;
    return this;
  }

  /**
   * Sets how often the condition should be evaluated.
   *
   * <p>
   * In reality, the interval may be greater as the cost of actually evaluating a condition function
   * is not factored in. The default polling interval is {@link #DEFAULT_WAIT_DURATION}.
   *
   * @param interval The timeout duration.
   * @return A self reference.
   */
  public FluentWait<T> pollingEvery(Duration interval) {
    this.interval = interval;
    return this;
  }

  /**
   * Configures this instance to ignore specific types of exceptions while waiting for a condition.
   * Any exceptions not whitelisted will be allowed to propagate, terminating the wait.
   *
   * @param types The types of exceptions to ignore.
   * @param <K>   an Exception that extends Throwable
   * @return A self reference.
   */
  public <K extends Throwable> FluentWait<T> ignoreAll(Collection<Class<? extends K>> types) {
    ignoredExceptions.addAll(types);
    return this;
  }

  /**
   * @param exceptionType exception to ignore
   * @return a self reference
   * @see #ignoreAll(Collection)
   */
  public FluentWait<T> ignoring(Class<? extends Throwable> exceptionType) {
    return this.ignoreAll(ImmutableList.<Class<? extends Throwable>>of(exceptionType));
  }

  /**
   * @param firstType  exception to ignore
   * @param secondType another exception to ignore
   * @return a self reference
   * @see #ignoreAll(Collection)
   */
  public FluentWait<T> ignoring(Class<? extends Throwable> firstType,
                                Class<? extends Throwable> secondType) {

    return this.ignoreAll(ImmutableList.of(firstType, secondType));
  }

  /**
   * Repeatedly applies this instance's input value to the given function until one of the following
   * occurs:
   * <ol>
   * <li>the function returns neither null nor false</li>
   * <li>the function throws an unignored exception</li>
   * <li>the timeout expires</li>
   * <li>the current thread is interrupted</li>
   * </ol>
   *
   * @param isTrue the parameter to pass to the {@link ExpectedCondition}
   * @param <V>    The function's expected return type.
   * @return The function's return value if the function returned something different
   * from null or false before the timeout expired.
   * @throws TimeoutException If the timeout expires.
   */
  @Override
  public <V> V until(Function<? super T, V> isTrue) {
    Instant end = clock.instant().plus(timeout);

    Throwable lastException;
    while (true) {
      try {
        V value = isTrue.apply(input);
        if (value != null && (Boolean.class != value.getClass() || Boolean.TRUE.equals(value))) {
          return value;
        }

        // Clear the last exception; if another retry or timeout exception would
        // be caused by a false or null value, the last exception is not the
        // cause of the timeout.
        lastException = null;
      } catch (Throwable e) {
        lastException = propagateIfNotIgnored(e);
      }

      // Check the timeout after evaluating the function to ensure conditions
      // with a zero timeout can succeed.
      if (end.isBefore(clock.instant())) {
        String message = messageSupplier != null ?
                         messageSupplier.get() : null;

        String timeoutMessage = String.format(
            "Expected condition failed: %s (tried for %d second(s) with %d milliseconds interval)",
            message == null ? "waiting for " + isTrue : message,
            timeout.getSeconds(), interval.toMillis());
        throw timeoutException(timeoutMessage, lastException);
      }

      try {
        sleeper.sleep(interval);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new WebDriverException(e);
      }
    }
  }

  private Throwable propagateIfNotIgnored(Throwable e) {
    for (Class<? extends Throwable> ignoredException : ignoredExceptions) {
      if (ignoredException.isInstance(e)) {
        return e;
      }
    }
    Throwables.throwIfUnchecked(e);
    throw new RuntimeException(e);
  }

  /**
   * Throws a timeout exception. This method may be overridden to throw an exception that is
   * idiomatic for a particular test infrastructure, such as an AssertionError in JUnit4.
   *
   * @param message       The timeout message.
   * @param lastException The last exception to be thrown and subsequently suppressed while waiting
   *                      on a function.
   * @return Nothing will ever be returned; this return type is only specified as a convenience.
   */
  protected RuntimeException timeoutException(String message, Throwable lastException) {
    throw new TimeoutException(message, lastException);
  }
}

3.2語法

巨集哥從原始碼中的Sample usage提取FluentWait的使用語法如下:

Wait wait = new FluentWait(WebDriver reference)
.withTimeout(timeout, SECONDS)
.pollingEvery(timeout, SECONDS)
.ignoring(Exception.class);

WebElement foo=wait.until(new Function<WebDriver, WebElement>() {
public WebElement applyy(WebDriver driver) {
return driver.findElement(By.id("foo"));
}
});

3.3例子

有了語法,按照語法寫一個簡單例子,如下:

Wait wait = new FluentWait<WebDriver>(driver)
.withTimeout(45, TimeUnit.SECONDS)
.pollingevery(5, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class);

FluentWait主要使用兩個引數–超時值(withTimeout)和輪詢頻率(pollingevery)。在上面的語法中,我們將超時值設定為45秒,輪詢頻率設定為5秒。等待條件的最長時間(45秒)和檢查指定條件成功或失敗的頻率(5秒)。如果元素在此時間範圍內可以查詢到,它將執行下一步操作,否則它將丟擲“ElementNotVisibleException”。

3.4完整程式碼

簡單例子的完整程式碼如下:

package lessons;
 
import java.util.concurrent.TimeUnit;
 
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
 
import com.google.common.base.Function;
 
public class FluentWait {
    public static void main(String[] args) throws Exception {
        
        System.setProperty("webdriver.chrome.driver", ".\\Tools\\chromedriver.exe");
        
        WebDriver driver = new ChromeDriver();
        
        driver.get("www.test.com");
        driver.manage().window().maximize();
        
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
                
                   .withTimeout(45, TimeUnit.SECONDS)
             
                   .pollingEvery(5, TimeUnit.SECONDS)
             
                   .ignoring(NoSuchElementException.class);
             
        WebElement ele1 = wait.until(new Function<WebDriver, WebElement>() {
             
             public WebElement apply(WebDriver driver) {
         
               return driver.findElement(By.id("xxxxxxx"));
         
             }
         
           });     
        
 
    }
 
}

4.專案實戰

由於沒有現成的網頁或者網站以供巨集哥進行演示,因此巨集哥自己簡單寫了一個demo以便演示使用。

4.1測試網頁程式碼

巨集哥這個網頁主要思想就是點選按鈕後10s倒計時,倒計時結束出現元素(一段英文文字)。

測試網頁test.html,參考程式碼如下所示:

<html>
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<title>北京-巨集哥</title> 
</head> 
<style>
#click {
    background-color: #4CAF50;
    border: none;
    color: white;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 16px;
    margin: 4px 2px;
    cursor: pointer;
}
.button {
    background-color: #f44336; 
    border: none;
    color: white;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 28px;
    margin-bottom: 100px;
}
#myAnchor
{
  text-decoration:none;
  color: white;
}
</style>
<body>
    <div style=" text-align:center;">
        <div style="height: 100px;margin-top: 200px;">
        <button class="button"><a id="myAnchor" href="https://www.cnblogs.com/du-hong/">北京-巨集哥</a></button></br>
        <input type="button" value="Click Me - Fluent Wait" id="click" onclick="foo(this, 10000);"/>
        <p style='color:red; font-family: verdana; font-size: 20;align="center";' id="demo">Click and Wait for <b>10 seconds</b> to view a message - "Software Testing Material - DEMO PAGE"</p>
        </div>
    </div>     
</body>
<script>
function foo(obj, time) {
    obj.disabled = true;

    setTimeout(function() {
        var x = setInterval(function(){
                time= time - 1000; //reduce each second
                obj.value = (time/1000)%60;
                if(time==0){
                        clearInterval(x);
                        obj.value = document.getElementById("demo").innerHTML = "Software Testing Material - DEMO PAGE";
                        obj.disabled = false;
                }
        }, 1000);
    }, time-10000);
}    

</script>
</html>

下邊巨集哥編寫java測試指令碼。

4.2程式碼設計

設計思路:開啟網頁後,點選按鈕開始5s頻率的輪訓查詢元素,第一次沒有找到,第二次10s剛好出現,程式碼也輪訓查詢也剛結束,沒有找到,等到第三次英文文字出現了,程式碼也查詢到,結束輪訓,繼續下一步操作。程式碼設計如下圖所示:

4.3Java參考程式碼

巨集哥首頁用單元測試Junit測試一下寫的方法有沒有問題,沒有問題,然後再呼叫。

4.3.1執行程式碼

1.執行程式碼,右鍵Run AS->JUnit Test,控制檯輸出,綠色的進度條證明寫的方法沒有問題,而且控制檯也迴圈了2次(每次5s,一共10s),等待到了元素的出現並將其打印出來。如下圖所示:

2.執行程式碼後電腦端的瀏覽器的動作,如下小視訊所示:

4.4Java優化參考程式碼

通過上邊的單元測試我們知道寫的方法沒有問題,那麼下邊我們直接呼叫該方法即可。優化後代碼如下:

package lessons;
import org.junit.Test;

import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.FluentWait;

import com.google.common.base.Function;

/**
 * @author 北京-巨集哥
 * 
 *《手把手教你》系列技巧篇(二十五)-java+ selenium自動化測試-FluentWait(詳細教程)
 *
 * 2021年8月31日
 */
public class FluentWaitClass {
    
    @Test
    public static void fluentWaitMethod(){
        System.setProperty("webdriver.gecko.driver", ".\\Tools\\chromedriver.exe"); //指定驅動路徑
        WebDriver driver = new ChromeDriver();
        //最大化視窗  
        long startTime = System.currentTimeMillis(); //獲取開始時間
        driver.manage().window().maximize();  
        long endTime = System.currentTimeMillis(); //獲取結束時間
        System.out.println("程式執行時間1:" + (endTime - startTime) + "ms"); //輸出程式執行時間
        driver.get("file:///C:/Users/DELL/Desktop/test/test.html");
        driver.findElement(By.xpath("//*[@id='click']")).click();
        
        
        FluentWait<WebDriver> wait = new FluentWait<WebDriver>(driver)
           .withTimeout(45, TimeUnit.SECONDS)
           .pollingEvery(5, TimeUnit.SECONDS)
           .ignoring(NoSuchElementException.class);
        long startTime1 = System.currentTimeMillis(); //獲取開始時間
        WebElement element = wait.until(new Function<WebDriver, WebElement>() {
            public WebElement apply(WebDriver driver) {
                WebElement element = driver.findElement(By.xpath("//*[@id='demo']"));
                String getTextOnPage = element.getText();
                if(getTextOnPage.equals("Software Testing Material - DEMO PAGE")){
                    System.out.println(getTextOnPage);
                    return element;
                }else{
                    System.out.println("FluentWait Failed");
                    return null;
                }
            }
        });
        long endTime1 = System.currentTimeMillis(); //獲取結束時間
        System.out.println("程式執行時間3:" + (endTime1 - startTime1) + "ms"); //輸出程式執行時間
        //driver.close();
    }
    
    public  static  void  main(String [] args){
        long startTime = System.currentTimeMillis(); //獲取開始時間
        fluentWaitMethod();
        long endTime = System.currentTimeMillis(); //獲取結束時間
        System.out.println("程式執行時間2:" + (endTime - startTime) + "ms"); //輸出程式執行時間
    }
}
4.4.1執行程式碼

1.執行程式碼,右鍵Run AS->java Application,控制檯輸出,如下圖所示:

2.執行程式碼後電腦端的瀏覽器的動作,如下小視訊所示:

5.小結

1.在設計程式碼過程中會報錯:Type mismatch: cannot convert from Test to Annotation 如下圖所示:

查了好多都說是:類名不能和註解名稱相同的原因。後來巨集哥檢查了一下,不相同啊,但是巨集哥為啥這裡還會報這個錯了。原來是巨集哥沒有匯入單元測試的包,但是也沒有提示匯入包,因此巨集哥將包匯入,程式碼錯誤消失。如下圖所示:

  好了,今天就分享到這裡了,感謝你耐心的閱讀!



感謝您花時間閱讀此篇文章,如果您覺得這篇文章你學到了東西也是為了犒勞下博主的碼字不易不妨打賞一下吧,讓博主能喝上一杯咖啡,在此謝過了!
如果您覺得閱讀本文對您有幫助,請點一下左下角“推薦”按鈕,您的將是我最大的寫作動力!另外您也可以選擇關注我,可以很方便找到我!
本文版權歸作者和部落格園共有,來源網址:https://www.cnblogs.com/du-hong 歡迎各位轉載,但是未經作者本人同意,轉載文章之後必須在文章頁面明顯位置給出作者和原文連線,否則保留追究法律責任的權利!