1. 程式人生 > 實用技巧 >Selenium隱式等待與顯示等待的選擇

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

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

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

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

1 public static void sleep(int sec) {
2 try {
3 Thread.sleep((long)(sec * 1000));
4 } catch (InterruptedException ) {
5 .printStackTrace();
6 }
7 }

其次,隱式等待。只要設定一次,在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、顯式等待,等待條件靈活,程式碼量稍多,每次定位元素都需要單獨呼叫。可使用封裝的方式解決。
所以在實際工作中,我們將顯式等待和執行緒等待結合使用。隱式等待則一般不用。