本文章是由機器翻譯。

測試回合

利用 WebBrowser 控制項進行 Web UI 測試自動化

James McCaffrey

下載代碼示例

在本月的專欄中,我將向您介紹為 Web 應用程式創建 UI 測試自動化的新方法。我要演示的方法適用于一種很常見卻又很棘手的情況:如何處理 Web 應用程式生成的模式訊息方塊。.

要想瞭解我將講述的內容,最好是看一下圖 12 中的螢幕擷取畫面。图 1 中的圖像是 Internet Explorer 中託管的簡單但很有代表性的 Web 應用程式。該應用程式接受使用者在文字方塊中的輸入,在使用者按一下標有“Click Me”的按鈕後,該應用程式標識輸入項的顏色,然後在第二個文字方塊中顯示結果。

 

圖 1 待測試的示例 Web 應用程式

請注意,當使用者按一下“Click Me”按鈕後,應用程式的邏輯會檢查使用者輸入框是否為空。如果為空,該應用程式會生成一個模式訊息方塊,並顯示一條錯誤消息。訊息方塊不易處理,部分原因在於,訊息方塊不屬於流覽器,也不屬於流覽器文檔。

現在,讓我們看一下 圖 2 中的示例測試運行。測試工具是 Windows 表單應用程式。Windows 表單應用程式中嵌入了一個 WebBrowser 控制項,Windows Forms 通過該控制項顯示和處理待測試的虛擬 Web 應用程式。

图 2 示例测试运行

如果在 Windows 表單工具底部查看 ListBox 控制項中的消息,可以看到,該測試工具首先將待測試的 Web 應用程式載入到 WebBrowser 控制項中。接下來,該工具使用單獨的執行執行緒來監視並處理 Web 應用程式生成的所有訊息方塊。該工具類比使用者按一下 Web 應用程式的“Click Me”按鈕,從而創建模式錯誤訊息方塊。觀察程式執行緒查找訊息方塊並類比使用者按一下,使訊息方塊消失。最後,測試工具類比使用者在第一個輸入框中鍵入“roses”,按一下“Click Me”按鈕,然後在第二個文字方塊中查找測試用例預期的回應“red”。

在本文中,我將簡要介紹待測試的示例 Web 應用程式。然後,將逐步介紹 Windows 表單測試工具的代碼,以便您根據測試方案的需要,對這些代碼進行修改。最後,我將介紹哪些情況下適用此方法,哪些情況下適用其他方法。

本文假設您具備基本的 Web 開發技能和中級 C# 編碼技能,不過,即使您是 C# 初學者,也應該能理解相關內容。相信您會發現,本文仲介紹的方法是對您的個人軟體測試、開發和管理工具包的有用補充。

待測試的應用程式

我們來看看作為測試自動化目標的示例 Web 應用程式的代碼。為簡單起見,我使用記事本來創建應用程式。應用程式功能由用戶端 JavaScript 而不是伺服器端處理來提供。稍後我將介紹,此測試自動化方法適用于基於大多數 Web 技術(如 ASP.NET、Perl/CGI 等)的應用程式,但最適用于使用 JavaScript 生成訊息方塊的應用程式。完整的 Web 應用程式碼如圖 3 所示。

图 3 Web 应用程序

    <html>
    <head>
    <title>Item Color Web Application</title>
    <script language="JavaScript">
      function processclick() {
        if (document.all['TextBox1'].value == "") {
          alert("You must enter an item into the first box!");
        }
        else {
          var txt = document.all['TextBox1'].value;
          if (txt == "roses")
            document.all["TextBox2"].value = "red";
          else if (txt == "sky")
            document.all["TextBox2"].value = "blue";
          else
            document.all["TextBox2"].value = "I don't know that item";
        }
      }
    
    </script>
    </head>
    <body bgcolor="#F5DEB3">
      <h3>Color Identifier Web Application</h3>
      <p>Enter an item: 
        <input type="text" id="TextBox1" /></p>
      <p><input type="button" value="Click Me" 
                id="Button1" 
                onclick="processclick()"/></p>
      <p>Item color is: 
        <input type="text" id="TextBox2" /></p>
    </body>
    </html>

我將 Web 應用程式命名為 default.html,保存在測試主機上 C:\Inetpub\wwwroot 目錄中的 ColorApp 目錄中。 為了避免出現安全問題,如果測試自動化直接在充當託管待測試應用程式的 Web 伺服器的電腦上運行時,此處介紹的方法效果最佳。 為了使 Web 應用程式簡單又能說明測試自動化的詳細資訊,我走了些捷徑(如不進行錯誤檢查),這些做法是不會在生產 Web 應用程式中出現的。

Web 應用程式功能的核心包含在名為 processclick 的 JavaScript 函數中。 當使用者按一下應用程式中 ID 為 Button1 且標記值為“Click Me”的按鈕控制項時,將調用該函數。Processclick 函數首先檢查輸入元素 TextBox1 中的值是否為空, 如果為空,則使用 JavaScript 警告函數生成錯誤訊息方塊。 如果 TextBox1 輸入元素不為空,則 processclick 函數使用 if-then 語句為 TextBox2 元素生成值。 請注意,Web 應用程式的功能由用戶端 JavaScript 提供,並且應用程式不執行多次用戶端到伺服器的往返,因此,只在每個功能迴圈中載入一次 Web 應用程式。

Windows 表單測試工具

現在,我們將逐步介紹圖 2 所示的測試工具代碼,以便您根據自己的需要修改代碼。 測試工具是一個 Windows 表單應用程式;通常我會使用 Visual Studio 來創建該程式。 Visual Studio 中自動生成的代碼很方便,但會隱藏一些重要概念,因此,我將向您演示如何使用記事本和命令列 C# 編譯器創建工具。 只要理解了我的示例代碼,則使用 Visual Studio 代替記事本不會遇到任何問題。

我打開記事本,通過聲明所使用的命名空間開始創建工具:

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;

您需要 System、Forms 和 Drawing 命名空間來實現基本的 Windows 表單功能。 通過使用 InteropServices 命名空間,測試工具可以使用 P/Invoke 機制查找並處理模式訊息方塊。 P/Invoke 可用於創建調用本機 Win32 API 函數的 C# 包裝方法。 Threading 命名空間用於衍生一個單獨的執行緒來監視訊息方塊的外觀。

接下來,聲明一個工具命名空間,開始編寫主 Windows 表單應用程式類的代碼,該類繼承自 System.Windows.Forms.Form 類:

namespace TestHarness
{
  public class Form1 : Form
  {
    [DllImport("user32.dll", 
      EntryPoint="FindWindow",
      CharSet=CharSet.Auto)]
    static extern IntPtr FindWindow(
      string lpClassName,
      string lpWindowName);
.
.
.

在 Form1 定義中的開始處,我放置了一個類作用域的屬性,該屬性允許測試工具調用位於 user32.dll 中的外部 FindWindow API 函數。 FindWindow API 函數會映射到名稱同為 FindWindow 的 C# 方法,該方法將接受視窗控制項的內部名稱並返回該控制項的 IntPtr 控制碼。 測試工具將使用 FindWindow 方法獲取待測試的 Web 應用程式生成的訊息方塊的控制碼。

接下來,再添加兩個屬性,以啟用其他 Win32 API 功能:

[DllImport("user32.dll", EntryPoint="FindWindowEx",
  CharSet=CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtr hwndParent,
  IntPtr hwndChildAfter, string lpszClass, 
  string lpszWindow);

[DllImport("user32.dll", EntryPoint="PostMessage",
  CharSet=CharSet.Auto)]
static extern bool PostMessage1(IntPtr hWnd, uint Msg,
  int wParam, int lParam);

與 FindWindowEx API 函數關聯的 C# FindWindowEx 方法將用於獲取 FindWindow 所找到的控制項的子控制項,即訊息方塊中的 OK 按鈕。 與 PostMessage API 函數關聯的 C# PostMessage1 方法將用於發送滑鼠按鈕彈起和按下消息,即按一下 OK 按鈕。

然後,聲明三個屬於 Windows 表單工具的類作用域控制項:

private WebBrowser wb = null;
private Button button1 = null;
private ListBox listBox1 = null;

WebBrowser 控制項是本機代碼的託管代碼包裝,用於容納 Internet Explorer Web 流覽器的功能。 WebBrowser 控制項公開可對該控制項中容納的網頁進行檢查和處理的方法和屬性。 Button 控制項將用於啟動測試自動化,ListBox 控制項將用於顯示測試工具記錄消息。

接下來,開始編寫 Form1 構造函數的代碼:

public Form1() {
    // button1
    button1 = new Button();
    button1.Location = 
      new Point(20, 430);
    button1.Size = new Size(90, 23);
    button1.Text = "Load and Test";
    button1.Click += 
      new EventHandler(
      this.button1_Click);
  .
.
.

此代碼應該是非常容易理解的。 Visual Studio 能夠出色地生成 UI 樣板代碼,因此,您可能從未像這樣從頭開始編寫 Windows 表單 UI 代碼。 請注意處理方式:產生實體控制項,設置屬性,然後掛接事件處理常式方法。 WebBrowser 控制項的處理方式也是如此。 使用本文所述的測試自動化方法時,通過某種方式顯示記錄消息會很有用,ListBox 控制項恰好可提供該功能:

// listBox1
listBox1 = new ListBox();
listBox1.Location = new Point(10, 460);
listBox1.Size = new Size(460, 200);

接下來,設置 WebBrowser 控制項:

// wb
wb = new System.Windows.Forms.WebBrowser();
wb.Location = new Point(10,10);
wb.Size = new Size(460, 400);
wb.DocumentCompleted +=
  new WebBrowserDocumentCompletedEventHandler(ExerciseApp);

此處應注意,我將事件處理常式方法掛接到 DocumentCompleted 事件,因此在將待測試的 Web 應用程式完全載入到 WebBrowser 控制項中之後,執行控制將轉移給程式定義的方法 ExerciseApp(我還沒有編寫其代碼)。 這一點很重要,因為在幾乎所有情況下,載入 Web 應用程式都存在延遲,在應用程式完全載入之前,任何訪問該控制項中的 Web 應用程式的嘗試都會引發異常。

您可能已經猜出一種可以處理此情況的方法,即在測試工具中放置一條 Thread.Sleep 語句。 但是,由於測試工具和 WebBrowser 控制項都在同一執行執行緒中運行,因此 Sleep 語句將同時停止測試工具和 WebBrowser 載入。

我通過向 Form 物件附加使用者控制項完成了 Form1 構造函數的代碼:

// Form1
  this.Text = "Lightweight Web Application Windows Forms Test Harness";
  this.Size = new Size(500, 710);
  this.Controls.Add(wb);
  this.Controls.Add(button1);
  this.Controls.Add(listBox1);
} // Form1()

接下來,為 Windows Forms 工具中用於啟動測試自動化的按鈕控制項編寫事件處理常式方法的代碼:

private void button1_Click(object sender, EventArgs e) {
  listBox1.Items.Add(
    "Loading Web app under test into WebBrowser control");
  wb.Url = new Uri(
    "http://localhost/ColorApp/default.html");
}

在記錄消息後,我通過設置 WebBrowser 控制項的 Url 屬性來指示該控制項載入待測試的 Web 應用程式。 請注意,我已經對待測試應用程式的 URL 進行了硬編碼。 此處介紹的方法最適合輕量型、可釋放的測試自動化,與必須在較長時間段內使用測試自動化的情況相比,硬編碼參數值的缺點更少。

接下來,開始編寫 ExerciseApp 方法的代碼,該方法將在引發 DocumentCompleted 事件時接受執行的控制:

private void ExerciseApp(object sender, EventArgs e) {
  Thread thread = new Thread(new
    ThreadStart(WatchForAndClickAwayMessageBox));
  thread.Start();

ExerciseApp 方法包含大部分實際的測試工具邏輯。 首先生成與程式定義的方法 WatchForAndClickAwayMessageBox 關聯的新執行緒。 具體思路是,當 WebBrowser 控制項中待測試的 Web 應用程式生成模式訊息方塊時,所有測試工具的執行都將停止,直到該訊息方塊得到處理為止,這意味著測試工具無法直接處理訊息方塊。 因此,通過衍生可監視訊息方塊的單獨執行緒,該工具可間接處理訊息方塊。

接下來,記錄一條消息,然後類比使用者按一下 Web 應用程式的“Click Me”按鈕:

listBox1.Items.Add(
  "Clicking on 'Click Me' button");
HtmlElement btn1 = 
  wb.Document.GetElementById("button1");
btn1.InvokeMember("click");

GetElementById 方法接受載入到 WebBrowser 控制項中的文檔所包含的 HTML 元素的 ID。 InvokeMember 方法可用於觸發事件(如按一下和滑鼠懸停)。 Web 應用程式的 TextBox1 控制項中沒有文本,因此,Web 應用程式將生成錯誤訊息方塊,工具的 WatchForAndClickAwayMessageBox 方法將處理該訊息方塊,稍後將對此進行介紹。

現在,假定訊息方塊已得到處理,下麵繼續測試方案:

listBox1.Items.Add("Waiting to click away message box");
listBox1.Items.Add("'Typing' roses into TextBox1");
HtmlElement tb1 = wb.Document.GetElementById("TextBox1");
tb1.InnerText = "roses";

我使用 InnerText 屬性類比使用者在 TextBox1 控制項中鍵入“roses”。 其他可用於處理待測試 Web 應用程式的有用屬性包括 OuterText、InnerHtml 和 OuterHtml。

下麵通過類比使用者按一下 Web 應用程式的“Click Me”按鈕來繼續自動化過程:

listBox1.Items.Add(
  "Clicking on 'Click Me' button again");
btn1 = wb.Document.GetElementById("button1");
btn1.InvokeMember("click");

與前面的類比按一下不同,此時 TextBox1 控制項中有文本,因此,Web 應用程式的邏輯將在 TextBox2 控制項中顯示某些結果文本,測試工具可以檢查預期結果並記錄通過或失敗消息:

listBox1.Items.Add("Looking for 'red' in TextBox2");
HtmlElement tb2 = wb.Document.GetElementById("TextBox2");
string response = tb2.OuterHtml;
if (response.IndexOf("red") >= 0) {
  listBox1.Items.Add("Found 'red' in TextBox2");
  listBox1.Items.Add("Test scenario result: PASS");
}
else {
  listBox1.Items.Add("Did NOT find 'red' in TextBox2");
  listBox1.Items.Add("Test scenario result: **FAIL**");
}

請注意,HTML 回應將類似于 <input type=”text” value=”red” />,因此,我使用 IndexOf 方法在 OuterHtml 內容中搜索正確的預期結果。

下麵是 Web 應用程式模式訊息方塊的處理方法的定義:

private void WatchForAndClickAwayMessageBox() {
  IntPtr hMessBox = IntPtr.Zero;
  bool mbFound = false;
  int attempts = 0;
  string caption = "Message from webpage";
.
.
.

我聲明瞭一個訊息方塊控制碼;一個 Boolean 變數(在發現訊息方塊時發出通知),一個計數器變數(用來限制測試工具查找訊息方塊的次數,以防止無限迴圈),以及要查找的訊息方塊的標題。 儘管本示例中的訊息方塊標題很明顯,任何時候都可以使用 Spy++ 工具來驗證任何視窗控制項的標題屬性。

接下來,編寫監視迴圈編碼:

do {
  hMessBox = FindWindow(null, caption);
  if (hMessBox == IntPtr.Zero) {
    listBox1.Items.Add("Watching for message box .
.
. "
);
    System.Threading.Thread.Sleep(100);
    ++attempts;
  }
  else {
    listBox1.Items.Add("Message box has been found");
    mbFound = true;
  }
} while (!mbFound && attempts < 250);

我使用一個 do-while 迴圈重複嘗試獲取訊息方塊控制碼。 如果從 FindWindow 返回的結果為 IntPtr.Zero,則延遲 0.1 秒並使迴圈嘗試計數器遞增。 如果返回的結果不是 IntPtr.Zero,說明已獲得訊息方塊控制碼,可以退出 do-while 迴圈。 “attempts <250”條件將限制測試工具等待訊息方塊出現的時間。 根據 Web 應用程式的特性,可能需要修改延遲時間和最大嘗試次數。

退出 do-while 迴圈後,通過確定退出原因是發現訊息方塊還是工具超時,就完成了 WatchForAndClickAwayMessageBox 方法:

if (!mbFound) {
  listBox1.Items.Add("Did not find message box");
  listBox1.Items.Add("Test scenario result: **FAIL**");
}
else { 
  IntPtr hOkBtn = FindWindowEx(hMessBox, IntPtr.Zero, null, "OK");
  ClickOn(hOkBtn );
}

如果未發現訊息方塊,則將此歸類為測試用例失敗並記錄結果。 如果發現訊息方塊,則使用 FindWindowEx 方法獲取父訊息方塊控制項上的 OK 按鈕子控制項的控制碼,然後調用程式定義的説明器方法 ClickOn,其定義如下:

private void ClickOn(IntPtr hControl) {
  uint WM_LBUTTONDOWN = 0x0201;
  uint WM_LBUTTONUP = 0x0202;
  PostMessage1(hControl, WM_LBUTTONDOWN, 0, 0);
  PostMessage1(hControl, WM_LBUTTONUP, 0, 0);
}

Windows 消息常量 0201h 和 0202h 分別表示按下滑鼠左鍵和滑鼠左鍵彈起。 這裡使用掛接到前述 Win32 PostMessage API 函數的 PostMessage1 方法。

我的測試工具以定義工具 Main 方法入口點作為結束:

[STAThread]
private static void Main() {
  Application.Run(new Form1());
}

將測試工具保存為 Harness.cs 之後,我使用了命令列編譯器。 啟動特定的 Visual Studio 命令 Shell(它知道 csc.exe C# 編譯器的位置),導航到 Harness.cs 檔所在的目錄,然後發出以下命令:

C:\LocationOfHarness> csc.exe /t:winexe Harness.cs

/t:winexe 參數指示編譯器生成 Windows 表單可執行檔,而不是預設的主控台應用程式可執行檔。結果是名為 Harness.exe 的檔,可從命令列執行該檔。如前所述,您可能希望使用 Visual Studio 而不是記事本來創建基於 WebBrowser 控制項的測試自動化。

總結

本文介紹的示例提供了足夠的資訊,可以説明您開始編寫自己的基於 WebBrower 控制項的測試自動化程式。此方法最適合輕量自動化方案,在這類方案中,您需要快速啟動並運行測試自動化,並且自動化的預期生命週期較短。這種方法的優勢是可以處理模式訊息方塊,這是其他 UI 測試自動化方法難於輕鬆處理的。在測試主要由用戶端 JavaScript 生成的 Web 應用程式功能時,此方法特別有用。

如果 Web 應用程式功能是通過多次用戶端-伺服器往返生成的,您必須對本文中的代碼進行修改,因為每當從 Web 伺服器返回回應時,都會觸發 DocumentCompleted 事件。一種處理方法是創建並使用一個變數,以跟蹤 DocumentCompleted 事件數並向工具添加分支邏輯。

Dr. James McCaffrey供職于 Volt Information Sciences, Inc.,在該公司他負責管理對華盛頓州雷蒙德市沃什灣 Microsoft 總部園區的軟體工程師進行的技術培訓。 他參與過多項 Microsoft 產品的研發工作,包括 Internet Explorer 和 MSN Search。. McCaffrey 博士是《.NET 軟體測試自動化之道》(Apress,2006)一書的作者,您可以通過以下位址與他聯繫: jammc@microsoft.com

衷心感謝以下 Microsoft 技術專家對本文的審閱:Paul NewsonDan Liebling