Selenium 隱式等待與顯示等待的選擇

fiskeryang發表於2020-08-17

很多朋友在剛接觸 Selenium 隱式等待與顯示等待時可能會有一些困惑,這兩種方式到底有什麼優劣,我們應該在何種情況下選擇哪種等待方式?
下面我們來分析一下這它們各有什麼特點。

一般來說,使用 selenium 實現自動化測試時可能會用到三種等待方式 :
1、Thread.sleep 執行緒等待
2、selenium 提供的隱式等待
3、selenium 提供的顯式等待

首先,執行緒等待很簡單,執行時會阻塞整個執行緒,而且必須要等到等待時間過完才能繼續向下執行,一般我們在自動化測試中可以作為步驟執行之間的一個固定間隔來使用,比如每一步操作之間可以固定設一個 0.5~1 秒的間隔時間,以避免操作速度太快造成一些意料之外的問題。可以把它封裝起來方便呼叫。

public static void sleep(int sec) {
    try {
        Thread.sleep((long)(sec * 1000));
    } catch (InterruptedException ) {
        .printStackTrace();
    }
}

其次,隱式等待。只要設定一次,在 WebDriver 例項的整個生命週期都是生效的,並且相對於執行緒等待,這個只要一旦發現了元素在 DOM 樹中出現就可以繼續向下執行。

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);

寫起來是挺方便的。但是我們來看一下 selenium 框架中對於 implicitlyWait 方法是如何描述的

/**
 * Specifies the amount of time the driver should wait when searching for an element if it is
 * not immediately present.
 * When searching for a single element, the driver should poll the page until the element has
 * been found, or this timeout expires before throwing a {@link NoSuchElementException}. When
 * searching for multiple elements, the driver should poll the page until at least one element
 * has been found or this timeout has expired.
 * Increasing the implicit wait timeout should be used judiciously as it will have an adverse
 * effect on test run time, especially when used with slower location strategies like XPath.
 */

尋找單個元素時,會尋找元素一直到找到或者超時。尋找多個元素時,找到至少一個符合條件的元素就會判定為成功繼而向下執行。
隱式等待要謹慎使用,因為這會對測試執行時間產生不利影響,尤其是與 XPath 等較慢的定位策略一起使用時

那麼問題來了,在真實的 UI 測試中我們經常會遇到一些這樣的情況,並不是需要找到這個元素就執行,而是有各種不同的執行條件,比如
等待某個元素消失,比如進度條
等待元素的屬性變化,比如 style,src,value 之類的屬性變為期望的值
等待元素能從 DOM 樹中找到並且可見、可操作
等待多個元素都符合期望的條件
多種複合條件需要同時滿足
類似於這些條件,只是使用隱式等待已經無法滿足我們的需求了。 那我們再來看看顯式等待
首先需要例項化一個 WebDriverWait 物件 (有三個構造方法過載,我們選一種常用的構造方法)
三個引數分別是 driver 例項,超時時長 (秒),輪詢間隔(毫秒)

WebDriverWait webDriverWait = new WebDriverWait(driver,5,1000);

然後呼叫 webDriverWait 的 until 方法,這個方法有兩個過載對應的返回值和引數都不同

public void until(com.google.common.base.Predicate<T> isTrue)
public <V> V until(com.google.common.base.Function<? super T, V> isTrue)

下面我們分別看一下這兩個方法的作用
第一個方法 返回值是 void 引數是一個 Predicate 介面,其作用是一直等待到 Predicate 中的 apply 方法返回 true 或者超時,再繼續向下執行
我們來執行一下下面這段程式碼,看看會發生什麼

webDriverWait.until(new Predicate<WebDriver>() {
    @Override
    public boolean apply(WebDriver webDriver) {
        System.out.println("Predicate等待");
        return false;
    }
});

console 輸出:
Predicate 等待
Predicate 等待
Predicate 等待
Predicate 等待
Predicate 等待
Predicate 等待

一共列印了 6 次 Predicate 等待,說明一共輪詢了 6 次中間間隔時間一共是 5 秒。
現在我們把上面程式碼的 apply 方法返回值改為 true,在執行一次看看

webDriverWait.until(new Predicate<WebDriver>() {
    @Override
    public boolean apply(WebDriver webDriver) {
        System.out.println("Predicate等待");
        return true;
    }
});

console 輸出:
Predicate 等待

可以看出,只輪詢了一次,因為 apply 返回了 true,跳出了輪詢繼續往下執行了。
所以使用 predicate 引數的這個 until 方法作用等待到符合使用者指定的條件再向下執行。
並且,該方法沒有返回值,也不會丟擲異常,等待的最長時間就是使用者設定的超時時長

第二個方法 返回值是一個泛型型別 V ,引數是一個函式介面 Function<? super T, V>
其中 ? super T 表示該引數必須是 T 或 T 的父類 V 表示該引數和返回值是相同的型別

WebElement ele = webDriverWait.until(new Function<WebDriver, WebElement>() {
    @Override
    public WebElement apply(WebDriver webDriver) {
        return driver.findElement(By.xpath(".//a[text()='新聞']"));
    }
});

這個顯示等待方法的作用和隱式等待是類似的,會一直輪詢直到找到符合定位條件的元素出現在 DOM 樹中,並返回該元素的物件。
我們把它改造一下,換成一個找不到的元素看看

WebElement ele = webDriverWait.until(new Function<WebDriver, WebElement>() {
    @Override
    public WebElement apply(WebDriver webDriver) {
        System.out.println("嘗試尋找元素");
        return driver.findElement(By.xpath(".//a[text()='新聞1']"));
    }
});

執行結果是 列印了六次 “嘗試尋找元素” 後,丟擲了一個 TimeOutException。
這樣我們可以看出,顯示等待的優勢就是由使用者自定義各種具體的等待條件,滿足實際工作中的各種需求。
Selenium 也提供了一些預置的等待條件,是由 Function 的子介面 ExpectedCondition 的封裝類 ExpectedConditions 來實現的,
我們來看看 ExpectedCondition 介面的定義

public interface ExpectedCondition<T> extends Function<WebDriver, T> {
}

可以看到 ExpectedCondition 與 function 的區別只是在於指定了第一個引數為 WebDriver 型別而已
ExpectedConditions 給開發者提供了許多內建的等待條件
常用的一般有以下這些

//等待元素可點選
webDriverWait.until(ExpectedConditions.elementToBeClickable(by));
//等待元素消失(不可見或從DOM樹中消失都算)
webDriverWait.until(ExpectedConditions.invisibilityOfElementLocated(by));
//等待元素可見,光是找到元素不行,必須得能看到,元素的長寬不為0
webDriverWait.until(ExpectedConditions.visibilityOf(by));
//等待元素的屬性包含指定的值
webDriverWait.until(ExpectedConditions.attributeContains("str"));
//等待所有元素可見
webDriverWait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(by));
//還有很多...

如果有個性化需求,比如需要同時滿足多個不同的需求的話,就只能自己實現 ExpectedCondition 介面的 apply 方法來實現了
由此可見,顯式等待相對隱式來說,功能要靈活得多。完全可以取代隱式等待的功能。只是相對來說,程式碼多了一點。我們可以採取封裝的方式來解決
比如下面這個方法封裝了等待元素可點選的操作,成功則返回元素物件,失敗丟擲異常

public static WebElement waitForElementClickable(WebDriver driver, final By by) throws Exception {
   WebElement element;
   try {
      element = new WebDriverWait(driver, 5, 1000).until(ExpectedConditions.elementToBeClickable(by));
   } catch (Exception e) {
      System.out.println("尋找元素失敗");
      throw e;
   }
   return element;
}

使用時只需要呼叫 waitForElementClickable 這個方法就行了。
最後我們可以總結一下各種等待方式適用的場景
1、執行緒等待,簡單粗暴,只適用於操作步驟之間的固定間隔,可提高頁面操作的穩定性。不過數值設定大了會嚴重影響指令碼的執行效率
2、隱式等待,使用簡單,且設定一次後在指定 Driver 例項的生命週期中全域性可用。但等待條件單一,且不適用於 xPath
3、顯式等待,等待條件靈活,程式碼量稍多,每次定位元素都需要單獨呼叫。可使用封裝的方式解決。
所以在我個人的實際工作中,會將顯式等待和執行緒等待結合使用。而隱式等待則一般不用。

相關文章