本文章是由機器翻譯。

Visual Studio 2013

透過 Visual Studio 2013 進行跨瀏覽器、自動程式碼 UI 測試

Damian Zapart

下载代码示例

基於 web 的解決方案有變得流行在過去幾年中,是因為他們向世界各地的使用者提供易於訪問。使用者也喜歡他們他們更為方便。使用者不需要安裝單獨的應用程式 ; 與瀏覽器單獨他們可以連接到他們的帳戶從任何連接到 Internet 的設備。然而,為軟體發展人員和測試人員,使用者可以選擇任何 Web 瀏覽器的事實提出了一個問題 — — 你必須測試的解決方案對多個瀏覽器。在本文中,我將演示如何解決此問題可能會在一種簡單方法通過創建編碼的 UI 測試案例針對任何現代的瀏覽器,將會執行,使用的只有 C#。

新的Visual Studio

幾年前,當Visual Studio2010 公佈時,其最有趣的特徵之一是測試基於 Web 的使用者介面解決方案的能力。然而,當時,有一些限制的使用這種技術 ; 例如,唯一支援的 Web 瀏覽器是互聯網資源管理器。此外,測試的 UI 依靠錄製使用者操作在 Web 網站上的,然後重播他們可以類比真實的使用者操作,其中許多開發人員認為不能接受。

新的Visual Studio2013 年可作為發佈候選版 (RC),帶來了一系列改進在許多不同的領域,從新的 IDE 功能到擴展的測試框架 (一長串的 RC 版本中的更改是可在 bit.ly/1bBryTZ)。從我的角度看,兩個新功能是特別有趣。第一,現在您可以測試不僅互聯網資源管理器中 (包括互聯網資源管理器中 11) 還所有其他現代瀏覽器,Google Chrome 和火狐瀏覽器的使用者介面。第二,從發展角度測試更關鍵,是 Microsoft 所稱的"的編碼的 UI 測試的瀏覽器上的可配置屬性"。基本上,這種新功能定義一組搜尋條件的 UI 元素。我將描述這些功能在本文中稍後介紹的更多詳細資訊。

測試中的系統

那些兩個新的功能是將用於創建跨瀏覽器的完全編碼的 UI 測試。我下的系統測試 (SUT),我想要一個公共的、 知名的、 基於 Web 的應用程式,所以我選擇了 Facebook。我想要涵蓋兩個基本的使用者方案。第一種方案是在成功登錄後將顯示設定檔頁面的正面測試案例。第二是負面測試案例中,我輸入不正確使用者憑據和重試登錄。在這種情況下,我希望在使用者的回應中的一些錯誤訊息。

還有我需要解決的幾個挑戰。第一,正確的瀏覽器需要將推出 (基於測試組態),和它必須能夠提供對特定 URL 的訪問。第二,在運行時,特定的控制項元素必須從要為類比的使用者提供的輸入的 HTML 文檔提取。只要需要,必須控制和正確的按鈕按一下提交到伺服器的 HTML 表單中輸入值。代碼也應能夠處理來自伺服器的回應,驗證它,並最後,關閉瀏覽器時 (在測試的清理方法) 中完成了測試案例。

在編碼之前

我在開始編碼之前我需要準備我的環境,是相當簡單的。首先要下載從Visual Studio2013 RC bit.ly/137Sg3U。預設情況下,Visual Studio2013 RC 允許您創建編碼的 UI 測試只是為互聯網資源管理器中,但這是不是我很感興趣 ; 我想要為所有現代瀏覽器創建測試。當然,會有沒有編譯錯誤,只要我表明我的代碼中測試應該針對非互聯網資源管理器中,瀏覽器運行,但在運行時期間將引發未處理的異常。稍後我會演示如何更改以及瀏覽器。為了避免在編碼期間的問題,我需要下載並安裝稱為"硒元件編碼 UI 跨瀏覽器測試的"Visual Studio擴展 (bit.ly/Sd7Pgw),這會讓我在我的電腦上安裝任何瀏覽器上執行的測試。

跳轉到代碼

一切都在的地方我現在可以演示如何創建一個新的編碼的 UI 專案。打開Visual Studio2013年並按一下檔 |新專案 |範本 |Visual C# |測試 |編碼的 UI 測試專案。輸入專案名稱,並按確定,看到新的解決方案,如中所示圖 1


圖 1 創建一個新的編碼的 UI 測試專案

該解決方案基本上分為三個強烈連接組,如中所示圖 2。第一組包含一個頁的命名空間,其中包括 ProfilePage 和 LoginPage 從中企業基頁類。這些類公開的屬性和邏輯的運行在瀏覽器中當前顯示的頁面上。這種方法可以説明拆分的測試案例實現從瀏覽器特定操作,如搜索的控制項的 id。測試案例直接操作的屬性和由頁面類公開的功能。


圖 2 編碼的 UI 測試的解決方案圖

第二組放在所有的擴展 (UIControlExtensions),選擇器 (SearchSelector) 和瀏覽器相關的常見類 (BrowserFactory,瀏覽器)。此物件的子集存儲 HTML 元素搜尋引擎 (我將描述不久) 的執行邏輯。我還添加了我自己的瀏覽器相關的物件,這有助於針對正確的 Web 瀏覽器中運行的測試案例。最後一組包含的測試檔案 (FacebookUITests) 與測試案例的實現。這些測試案例從未運行在瀏覽器上直接 ; 相反,他們使用的面板類。

我的專案的一個重要部分是 HTML 控制項搜尋引擎,因此,我的第一步是創建一個靜態的類稱為 UIControl­擴展,包含用於查找和提取特定控制項從當前打開的網頁瀏覽器上的執行邏輯。使編碼更容易 — — 和能夠以後在該專案中重用它 — — 我不想要每次需要使用它,所以我要將它作為將擴展內置的 UITestControl 類型的擴充方法實現初始化此類的實例。此外,我實現任何擴展函數將泛型。他們必須從 HtmlControl (編碼的 UI 測試框架中的所有使用者介面控制項的基類) 派生,並且必須包含一個預設的無參數建構函式。我想要此函數是通用的因為我打算只搜索特定的控制項類型 (請參閱可用的 html 清單類型在 bit.ly/1aiB5eW)。

我會通過 selectorDefinition,SearchSelector 類型的參數傳遞給我的函數的搜尋條件。SearchSelector 類是一個簡單的類型,但它仍然是非常有用。它公開的若干屬性,如 ID 或類,該類可以設置從另一個函數和後來被變換,使用反射到 PropertyExpressionCollection 類 (bit.ly/18lvmnd)。下一步,該屬性集合可用於作為篩選器提取 HTML 控制項的給定的條件相匹配的只是小的子集。後來,生成的屬性集合分配給 SearchProperties 屬性 (bit.ly/16C20iS) 的泛型物件,讓它調用 Exists 屬性和 FindMatchingControls 函數。請記住,編碼的 UI 測試框架演算法不會搜索的特定控制項整體上頁的預設情況下,並將處理所有兒童和 sub­兒童只能在擴展 UITestControl 上的。這有助於提高性能的測試案例,因為搜尋條件可以應用到的 HTML 文檔 ; 只是一個小的子集 例如,對一些 DIV 容器,如中所示的所有兒童圖 3

圖 3 搜索 HTML 控制項

private static ReadOnlyCollection<T> 
  FindAll<T>(this UITestControl control, 
  SearchSelector selectorDefinition) where T : HtmlControl, new()
{
  var result = new List<T>();
    T selectorElement = new T { Container = control };
      selectorElement.SearchProperties.AddRange(
        selectorDefinition.ToPropertyCollection());
    if (!selectorElement.Exists)
      {
        Trace.WriteLine(string.Format(
          "Html {0} element doesn't exist for given selector {1}.",
             typeof(T).Name, selectorDefinition),"UI CodedTest");
          return result.AsReadOnly();
      }
    return selectorElement
        .FindMatchingControls()
        .Select(c => (T)c).ToList().AsReadOnly();
      }
}

我實現了核心的搜索控制引擎,但該函數 FindAll < T > 需要大量的代碼和知識,以使其正常工作 — — 你要指定的搜索參數,檢查是否一個專案是否存在,等等。 這是為什麼我決定做私人和暴露其他兩個的功能改為:

public static T FindById<T>(this UITestControl control, 
  string controlId) where T : HtmlControl, new()
public static T FindFirstByCssClass<T>(this UITestControl control, 
  string className, bool contains = true) where T : HtmlControl, new()

這些泛型方法是有用得多,因為他們"單一目的"和減少預期投入到簡單類型的不同層面。 在引擎蓋下這兩個函式呼叫 FindAll < T > 功能和操作它的結果,但執行藏在他們的屍體。

使用任何瀏覽器

我已經提出一些努力進入查找和檢索控制項,但要測試是否我的函數實現正確我需要獲取 Web 瀏覽器工作,這意味著它需要推出。 啟動特定的瀏覽器是任何其他與瀏覽器相關的操作一樣簡單。 如前所述,我希望把所有的瀏覽器有關的業務,與頁面相關的類裡面。 但是,啟動瀏覽器不是測試的一部分 — — 它是一個先決條件。 軟體發展的最佳做法的動機,決定創建一個企業基頁類,所示圖 4、 沒有任何冗余存儲所有派生的頁類 (包括啟動瀏覽器) 的常用操作。

圖 4 企業基頁類

public abstract class BasePage : UITestControl
{
  protected const string BaseURL = "https://www.facebook.com/";
  /// <summary>
  /// Gets URL address of the current page.
/// </summary>
  public Uri PageUrl{get; protected set;}
  /// <summary>
  /// Store the root control for the page.
/// </summary>
  protected UITestControl Body;
  /// <summary>
  /// Gets current browser window.
/// </summary>
  protected BrowserWindow BrowserWindow { get; set; }
  /// <summary>
  /// Default constructor.
/// </summary>
  public BasePage()
  {
    this.ConstructUrl();
  }
  /// <summary>
  /// Builds derived page URL based on the BaseURL and specific page URL.
/// </summary>
  /// <returns>A specific URL for the page.</returns>
  protected abstract Uri ConstructUrl();
  /// <summary>
  /// Verifies derived page is displayed correctly.
/// </summary>
  /// <returns>True if validation conditions passed.</returns>
  public abstract bool IsValidPageDisplayed();
}

靜態的泛型發射 < T > 函數也是基地的一部分­頁類。 在函數體內,(從企業基頁派生) 的特定的頁面類型的新實例初始化基於參數-­較少的預設建構函式。 以後在代碼中,目標 Web 瀏覽器基於設置瀏覽器參數的值 ("ie"的互聯網資源管理器中,"鉻"谷歌瀏覽器等)。 此工作分配指定的瀏覽器將對其執行當前測試。 下一步是要導航到一些在選定瀏覽器中的 URL。 這被處理由 BrowserWindow.Launch (頁。ConstructUrl()),ConstructUrl 函數在哪裡特定函數的每個派生的頁面。 後發起瀏覽器視窗並導航到特定 URL,我存儲 HTML 正文內企業基頁屬性和 (可選) 將瀏覽器視窗最大化 (這是可取因為的頁的控制項可能會重疊和自動化的 UI 操作可能會失敗)。 我然後清除 cookie,因為每個測試應該獨立。 最後,在中所示的啟動函數圖 5,我想要檢查是否當前顯示的頁面是正確的所以我叫 IsValidPageDisplayed,將在泛型頁的上下文中執行。 此函數會查找所有所需的 HTML 控制項 (登錄名、 密碼,提交按鈕) 並驗證他們頁面中存在。

圖 5 發射功能

public static T Launch<T>(
  Browser browser = Browser.IE,
  bool clearCookies = true,
  bool maximized = true)
  where T : BasePage, new()
{
  T page = new T();
  var url = page.PageUrl;
  if (url == null)
  {
    throw new InvalidOperationException("Unable to find URL for requested page.");
  }
  var pathToBrowserExe = FacebookCodedUITestProject
        .BrowserFactory.GetBrowserExePath(browser);
  // Setting the currect browser for the test.
BrowserWindow.CurrentBrowser = GetBrowser(browser);
  var window = BrowserWindow.Launch(page.ConstructUrl());
  page.BrowserWindow = window;
  if (window == null)
  {
    var errorMessage = string.Format(
        "Unable to run browser under the path: {0}", pathToBrowserExe);
          throw new InvalidOperationException(errorMessage);
            }
            page.Body = (window.CurrentDocumentWindow.GetChildren()[0] as
                          UITestControl) as HtmlControl;
  if (clearCookies)
  {
  BrowserWindow.ClearCookies();
  }
  window.Maximized = maximized;
  if (!page.IsValidPageDisplayed())
  {
    throw new InvalidOperationException("Invalid page is displayed.");
  }
  return page;
}
 }

不斷演變的 web 瀏覽器,你可能沒有意識到,當發生這種情況。有時,這意味著某些功能不可用在新的瀏覽器版本中,從而導致一些測試失敗,即使他們通過以前。這就是為什麼它是重要的是要禁用瀏覽器自動更新並等待,直到編碼的 UI 測試的跨瀏覽器的新版本支援由硒元件。否則,意外的異常期間可能會發生運行時,如中所示圖 6


圖 6 在 Web 瀏覽器更新後的異常

測試、 測試、 測試

最後,我會為我的測試中寫一些邏輯。如前所述,我想要測試兩個基本的使用者場景。第一次是一個積極的登錄過程 (第二次負面測試案例是可用在專案原始程式碼中,其中你可以找到在 archive.msdn.microsoft.com/mag201312Testing)。若要運行此測試,必須創建從企業基頁,派生的特定頁類中所示圖 7。裡面我新的類,在私人領域,我放置所有的常量值 (控制項、 Id 和 CSS 類的名稱),並創建使用這些常量可以從當前頁面中提取特定的 UI 元素的專用的方法。我還創建了一個稱為 TypeCredentialAndClickLogin (登錄名的字串,字串的密碼),完全封裝登錄操作函數。在運行時,它查找所有所需的控制項、 類比輸入中的值作為參數,傳遞,然後按登錄按鈕下通過按一下滑鼠左鍵。

圖 7 登錄頁

public class LoginPage : BasePage
  {
    private const string LoginButtonId = "u_0_1";
    private const string LoginTextBoxId = "email";
    private const string PasswordTextBoxId = "pass";
    private const string LoginFormId = "loginform";
    private const string ErrorMessageDivClass = "login_error_box";
    private const string Page = "login.php";
    /// <summary>
    /// Builds URL for the page.
/// </summary>
    /// <returns>Uri of the specific page.</returns>
    protected override Uri ConstructUrl()
    {
      this.PageUrl = new Uri(string.Format("{0}/{1}", BasePage.BaseURL,
         LoginPage.Page));
      return this.PageUrl;
    }
    /// <summary>
    /// Validate that the correct page is displayed.
/// </summary>
    public override bool IsValidPageDisplayed()
    {
      return this.Body.FindById<HtmlDiv>(LoginTextBoxId) != null;
    }
    /// <summary>
    /// Gets the login button from the page.
/// </summary>
    public HtmlInputButton LoginButton
    {
      get
      {
        return this.Body.FindById<HtmlInputButton>(LoginButtonId);
      }
    }
    /// <summary>
    /// Gets the login textbox from the page.
/// </summary>
    public HtmlEdit LoginTextBox
    {
      get
      {
        return this.Body.FindById<HtmlEdit>(LoginTextBoxId);
      }
    }
    /// <summary>
    /// Gets the password textbox from the page.
/// </summary>
    public HtmlEdit PasswordTextBox
    {
      get
      {
        return this.Body.FindById<HtmlEdit>(PasswordTextBoxId);
      }
    }
    /// <summary>
    /// Gets the error dialog window - when login failed.
/// </summary>
    public HtmlControl ErrorDialog
    {
      get
      {
        return this.Body.FindFirstByCssClass<HtmlControl>("*login_error_box ");
      }
    }
    /// <summary>
    /// Types login and password into input fields and clicks the Login button.
/// </summary>
    public void TypeCredentialAndClickLogin(string login, string password)
    {
      var loginButton = this.LoginButton;
      var emailInput = this.LoginTextBox;
      var passwordInput = this.PasswordTextBox;
      emailInput.TypeText(login);
      passwordInput.TypeText(password);
      Mouse.Click(loginButton, System.Windows.Forms.MouseButtons.Left);
    }
  }

創建所需的元件後,我可以生成測試案例。 此測試函數將驗證登錄操作已成功完成。 在測試案例開始,啟動使用 < T > 發射的登錄頁 靜態函數。 我將所有需要的值傳遞到該登錄名和密碼輸入欄位,然後按一下登錄按鈕。 當操作完成後時,驗證新顯示的面板是一個設定檔頁面面:

[TestMethod]
public void FacebookValidLogin()
{
  var loginPage = BasePage.Launch<LoginPage>();
  loginPage.TypeCredentialAndClickLogin(fbLogin, fbPassword);
  var profilePage = loginPage.InitializePage<ProfilePage>();
  Assert.IsTrue(profilePage.IsValidPageDisplayed(),
     "Profile page is not displayed.");
}

同時搜索具有特定的 CSS 類的控制項時,我注意到在編碼的 UI 測試框架中可能會出現一種併發症。 在 HTML 中,控制項可以在類屬性中,有一個以上的類名稱,這當然會影響我工作的框架。 如果,例如,我當前的 Web 網站包含的 DIV 元素具有屬性類"A B C"和 SearchSelector.Class 屬性用於查找與"B"的 CSS 類的所有控制項,可能得不到任何的結果 — — 因為"A B C"並不等於"B"。要處理這個問題,我介紹明星"*"標記法,其中類期望從"等於"更改為"包含"。所以,若要獲取此示例工作,我需要更改"B"類類"* B."

如果。。。

有時測試失敗,你要問問為什麼。 在許多情況下,審查測試日誌是所有所需回答這個問題 — — 但並不總是。 在編碼的 UI 測試框架中,一項新功能提供的額外資訊的需求。

假設由於顯示頁的預期不同的是失敗的測試。 在日誌中,我看到一些需要找不到控制。 這是很好的資訊,但它不是給完整的答案。 與新的功能,但是,我可以捕獲當前顯示的螢幕。 若要使用該功能,我只是要添加捕獲和方式將其保存在測試清理方法中,如中所示圖 8。 現在我獲得任何失敗的測試的詳細的的資訊。

圖 8 測試清理方法

[TestCleanup]
public void TestCleanup()
{
  if (this.TestContext.CurrentTestOutcome != null &&
     this.TestContext.CurrentTestOutcome.ToString() == "Failed")
{
    try
    {
      var img = BrowserWindow.Desktop.CaptureImage();
      var pathToSave = System.IO.Path.Combine(
        this.TestContext.TestResultsDirectory,
        string.Format("{0}.jpg", this.TestContext.TestName));
      var bitmap = new Bitmap(img);
      bitmap.Save(pathToSave);
    }
    catch
    {
      this.TestContext.WriteLine("Unable to capture or save screen.");
    }
  }
}

總結

在這篇文章我如何快速顯示並且開始在Visual Studio2013年鋼筋混凝土中使用新的編碼的 UI 測試框架是容易。 當然,我描述了只有基本使用這種技術,包括管理不同的瀏覽器,並支援各種操作來查找、 檢索和操作 HTML 控制項。 有許多更多強大功能值得探討。

Damian Zapart 是一個.NET 開發者超過七年的經驗。他主要集中在基於 Web 的技術。他是個程式設計的怪人對尖端技術、 設計模式和測試感興趣。訪問他的博客在 bit.ly/18BV7Qx 閱讀更多關於他。

衷心感谢以下 Microsoft 技术专家对本文的审阅:比拉勒 A. Durrani 和 Evren Önem
比拉勒 A. Durrani (Microsoft) 是在 Microsoft 中送出中繼線歐洲團隊的測試工程師。 他的工作是雲的保證品質基於電視服務和用戶端 HTML5,影響數以百萬計的使用者。

Evren Onem (Microsoft) 是在 Microsoft 中送出中繼線歐洲團隊的開發人員。 他的工作是設計和構建基於雲計算的數以百萬計的最終使用者直播電視服務。 他最近的技術利益當中是 JavaScript 和基於 NodeJS 的伺服器端開發。 他也是認知無線電特設網路上的一夜時間研究員。