從 RC 遷移到 WebDriver

關於從 Selenium 1 更新到 Selenium 2 的資訊。

如何遷移到 Selenium WebDriver

在採用 Selenium 2 時,一個常見的問題是,當向現有的測試集添加新測試時,正確的做法是什麼? 框架的新用戶可以開始使用新的 WebDriver API 來編寫他們的測試。 但是,對於已經有現有測試套件的用戶來說呢? 本指南旨在示範如何將您現有的測試遷移到新的 API,以便所有新測試都可以使用 WebDriver 提供的新功能來編寫。

這裡介紹的方法描述了逐步遷移到 WebDriver API,而無需一次性大規模地重做所有事情。 這意味著您可以有更多時間遷移現有的測試,這可能會讓您更容易決定在哪裡投入精力。

本指南使用 Java 編寫,因為 Java 對於遷移具有最佳支援。 隨著我們為其他語言提供更好的工具,本指南將會擴展以包含這些語言。

為什麼要遷移到 WebDriver

將一組測試從一個 API 遷移到另一個 API 需要付出巨大的努力。 為什麼您和您的團隊會考慮進行此舉? 以下是一些您應該考慮遷移您的 Selenium 測試以使用 WebDriver 的原因。

  • 更小、更精簡的 API。 WebDriver 的 API 比原始的 Selenium RC API 更物件導向。 這可以使其更易於使用。
  • 更好地模擬使用者互動。 在可能的情況下,WebDriver 會使用原生事件與網頁互動。 這更貼近地模擬了您的使用者使用您的網站和應用程式的方式。 此外,WebDriver 還提供了進階使用者互動 API,讓您可以模擬與您網站的複雜互動。
  • 瀏覽器供應商的支持。 Opera、Mozilla 和 Google 都是 WebDriver 開發的積極參與者,並且每個公司都有工程師致力於改進框架。 通常,這意味著對 WebDriver 的支援已內建到瀏覽器本身中:您的測試運行速度盡可能快且穩定。

開始之前

為了使遷移過程盡可能輕鬆,請確保您的所有測試都能使用最新的 Selenium 版本正確運行。 這聽起來可能很明顯,但最好還是說出來!

開始使用

開始遷移的第一步是更改您獲取 Selenium 實例的方式。 當使用 Selenium RC 時,是這樣完成的

Selenium selenium = new DefaultSelenium("localhost", 4444, "*firefox", "http://www.yoursite.com");
selenium.start();

應該這樣替換

WebDriver driver = new FirefoxDriver();
Selenium selenium = new WebDriverBackedSelenium(driver, "http://www.yoursite.com");

後續步驟

一旦您的測試執行沒有錯誤,下一步就是遷移實際的測試程式碼以使用 WebDriver API。 根據您的程式碼抽象化的程度,這可能是一個簡短的過程或一個漫長的過程。 無論在哪種情況下,方法都是相同的,可以簡單地概括為:當您編輯程式碼時,修改程式碼以使用新的 API。

如果您需要從 Selenium 實例中提取底層的 WebDriver 實作,您可以簡單地將其轉換為 WrapsDriver

WebDriver driver = ((WrapsDriver) selenium).getWrappedDriver();

這允許您繼續像往常一樣傳遞 Selenium 實例,但在需要時解包 WebDriver 實例。

在某個時候,您的程式碼庫將主要使用較新的 API。 此時,您可以反轉關係,始終使用 WebDriver 並按需實例化 Selenium 實例

Selenium selenium = new WebDriverBackedSelenium(driver, baseUrl);

常見問題

幸運的是,您不是第一個經歷這種遷移的人,因此這裡列出了一些其他人遇到的常見問題,以及如何解決這些問題。

點擊和輸入更完整

Selenium RC 測試中的一個常見模式是看到類似這樣的程式碼

selenium.type("name", "exciting tex");
selenium.keyDown("name", "t");
selenium.keyPress("name", "t");
selenium.keyUp("name", "t");

這依賴於這樣一個事實:“type” 只是替換了已識別元素的內容,而不會觸發使用者與頁面互動時通常會觸發的所有事件。 最後直接調用 “key*” 會導致 JS 處理程序按預期觸發。

當使用 WebDriverBackedSelenium 時,填寫表單欄位的結果將是 “exciting texttt”:這不是您所期望的! 原因是因為 WebDriver 更準確地模擬了使用者行為,因此會一直觸發事件。

同樣的事實有時可能會導致頁面加載比在 Selenium 1 測試中更早觸發。 如果 WebDriver 拋出 “StaleElementException”,您可以判斷發生了這種情況。

WaitForPageToLoad 過早返回

發現頁面加載何時完成是一件棘手的事情。 我們是指 “當加載事件觸發時”、“當所有 AJAX 請求完成時”、“當沒有網路流量時”、“當 document.readyState 發生變化時” 還是完全是其他情況?

WebDriver 嘗試模擬原始的 Selenium 行為,但由於各種原因,這並不總是能完美地工作。 最常見的原因是,很難區分頁面加載尚未開始,以及頁面加載已在方法調用之間完成。 這有時意味著在頁面完成(甚至開始!)加載之前,控制權已返回給您的測試。

解決方案是等待一些特定的東西。 通常,這可能是為了您接下來要互動的元素,或者是為了將某些 Javascript 變數設定為特定值。 一個例子是

Wait<WebDriver> wait = new WebDriverWait(driver, Duration.ofSeconds(30));
WebElement element= wait.until(visibilityOfElementLocated(By.id("some_id")));

其中 “visibilityOfElementLocated” 的實作方式如下

public ExpectedCondition<WebElement> visibilityOfElementLocated(final By locator) {
  return new ExpectedCondition<WebElement>() {
    public WebElement apply(WebDriver driver) {
      WebElement toReturn = driver.findElement(locator);
      if (toReturn.isDisplayed()) {
        return toReturn;
      }
      return null;
    }
  };
}

這看起來可能很複雜,但幾乎都是樣板程式碼。 唯一有趣的部分是,“ExpectedCondition” 將被重複評估,直到 “apply” 方法返回既不是 “null” 也不是 Boolean.FALSE 的值。

當然,添加所有這些 “wait” 調用可能會使您的程式碼變得混亂。 如果是這種情況,並且您的需求很簡單,請考慮使用隱式等待

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

透過這樣做,每次定位元素時,如果元素不存在,則會重試定位,直到元素存在或直到 30 秒過去。

使用 XPath 或 CSS 選擇器不一定有效,但在 Selenium 1 中有效

在 Selenium 1 中,xpath 通常使用捆綁的函式庫而不是瀏覽器本身的功能。 除非沒有其他選擇,否則 WebDriver 將始終使用原生瀏覽器方法。 這意味著複雜的 xpath 表達式可能會在某些瀏覽器上中斷。

Selenium 1 中的 CSS 選擇器是使用 Sizzle 函式庫實作的。 這實作了 CSS 選擇器規範的超集,並且並不總是清楚您在哪裡越界。 如果您使用的是 WebDriverBackedSelenium 並且使用 Sizzle 定位器而不是 CSS 選擇器來尋找元素,則警告將記錄到控制台中。 值得花時間尋找這些警告,特別是如果測試由於無法找到元素而失敗。

沒有 Browserbot

Selenium RC 基於 Selenium Core,因此當您執行 Javascript 時,您可以訪問 Selenium Core 的位元以使事情更容易。 由於 WebDriver 不是基於 Selenium Core,因此這不再可能。 您如何判斷您是否正在使用 Selenium Core? 很簡單! 只需查看您的 “getEval” 或類似的調用是否在評估的 Javascript 中使用 “selenium” 或 “browserbot”。

您可能正在使用 browserbot 來獲取測試的當前視窗或文檔的句柄。 幸運的是,WebDriver 始終在當前視窗的上下文中評估 JS,因此您可以直接使用 “window” 或 “document”。

或者,您可能正在使用 browserbot 來定位元素。 在 WebDriver 中,執行此操作的慣用方法是首先定位元素,然後將其作為參數傳遞給 Javascript。 因此

String name = selenium.getEval(
    "selenium.browserbot.findElement('id=foo', browserbot.getCurrentWindow()).tagName");

變成

WebElement element = driver.findElement(By.id("foo"));
String name = (String) ((JavascriptExecutor) driver).executeScript(
    "return arguments[0].tagName", element);

請注意,傳遞的 “element” 變數如何作為 JS 標準 “arguments” 陣列中的第一項出現。

執行 Javascript 沒有返回任何內容

WebDriver 的 JavascriptExecutor 將包裝所有 JS 並將其評估為匿名表達式。 這意味著您需要使用 “return” 關鍵字

String title = selenium.getEval("browserbot.getCurrentWindow().document.title");

變成

((JavascriptExecutor) driver).executeScript("return document.title;");
最後修改於 2022 年 1 月 10 日:更多維基 (#907) [部署網站] (adcf706a1ad)