《软件测试自动化之道》读书笔记 之 底层的Web UI 测试
2014-09-28
测试自动化程序的任务
待测程序
测试程序
启动IE并连接到这个实例
如何判断待测web程序完全加载到浏览器
操纵并检查IE Shell
操作待测Web页面上的HTML元素的值
验证Web页面上HTML元素
示例代码
测试自动化程序的任务
底层技术的核心是,通过直接调用mshtml.dll和shdocvw.dll库来访问并且操纵IE客户区域的HTML对象。
待测程序
新建一个网站“WebAUT”,删除原来的Default.aspx,创建一个新的Default.aspx。在其中添加3个Label控件、2个RadioButton控件、1个TextBox控件、1个Button控件和1个ListBox控件。
图1 Web UAT
待测程序代码:
1 using System; 2 using System.Collections.Generic; 3 4 using System.Web; 5 using System.Web.UI; 6 using System.Web.UI.WebControls; 7 8 public partial class _Default : System.Web.UI.Page 9 { 10 private System.Collections.ArrayList al = new System.Collections.ArrayList(); 11 protected void Page_Load(object sender, EventArgs e) 12 { 13 Product p1 = new Product("widgets", "1A11A", 11.11); 14 Product p2 = new Product("gadgets", "2B22B", 22.22); 15 Product p3 = new Product("foozles", "3C33C", 33.33); 16 17 al.Add(p1); 18 al.Add(p2); 19 al.Add(p3); 20 Label3.Text = "Search Complete"; 21 Label3.Visible = false; 22 } 23 protected void Button1_Click(object sender, EventArgs e) 24 { 25 ListBox1.Items.Clear(); 26 string filter = TextBox1.Text; 27 ListBox1.Items.Add("ProdName ProdID Price"); 28 ListBox1.Items.Add("====================="); 29 30 if (RadioButtonList1_1.Checked) 31 { 32 foreach (Product p in al) 33 { 34 if (p.name.IndexOf(filter) >= 0) 35 ListBox1.Items.Add(p.name + ", " + p.id + ", " + p.price); 36 } 37 } 38 else if (RadioButtonList1_2.Checked) 39 { 40 foreach (Product p in al) 41 { 42 if (p.id.IndexOf(filter) >= 0) 43 ListBox1.Items.Add(p.name + ", " + p.id + ", " + p.price); 44 } 45 } 46 Label3.Visible = true; 47 } 48 49 public class Product 50 { 51 public string name; 52 public string id; 53 public double price; 54 public Product(string name, string id, double price) 55 { 56 this.name = name; 57 this.id = id; 58 this.price = price; 59 } 60 } 61 }
测试程序
启动IE并连接到这个实例
1 static void Main(string[] args) 2 { 3 //... 4 5 SHDocVw.InternetExplorer ie = null; 6 Console.WriteLine(" Launching an instance of IE"); 7 Process p = Process.Start("iexplore.exe", "about:blank"); 8 System.Threading.Thread.Sleep(2000); 9 if (p == null) 10 throw new Exception("Could not launch IE"); 11 Console.WriteLine("Process handle = " + p.MainWindowHandle.ToString()); 12 13 SHDocVw.ShellWindows allBrowsers = new SHDocVw.ShellWindows(); 14 Console.WriteLine("Number active browsers = " + allBrowsers.Count); 15 16 if (allBrowsers.Count == 0) 17 throw new Exception("Cannot find IE"); 18 19 Console.WriteLine("Attaching to IE"); 20 int i = 0; 21 22 while (i < allBrowsers.Count && ie == null) 23 { 24 InternetExplorer e = (InternetExplorer)allBrowsers.Item(i); 25 if (e != null) 26 { 27 if (e.HWND == (int)p.MainWindowHandle) 28 ie = e; 29 } 30 ++i; 31 } 32 33 if (ie == null) 34 throw new Exception("Failed to attach to IE"); 35 36 //... 37 }
如何判断待测web程序完全加载到浏览器
1 static void Main(string[] args) 2 { 3 SHDocVw.InternetExplorer ie = null; 4 //把ie对象连接到IE程序所在的进行 5 6 ie.DocumentComplete += new DWebBrowserEvents2_DocumentCompleteEventHandler(ie_DocumentComplete); 7 8 Console.WriteLine(" Navigating to the Web app"); 9 object nil = new object(); 10 ie.Navigate("http://localhost:30614/WebAUT/Default.aspx", ref nil, ref nil, ref nil, ref nil); 11 12 documentComplete.WaitOne(); 13 14 //... 15 } 16 private static void ie_DocumentComplete(object pDisp, ref object URL) 17 { 18 documentComplete.Set(); 19 }
测试程序有可能在待测程序尚未完全加载的情况下试图对其进行操作,这么做,很可能引发异常。测试程序可通过事件DocumentComplete和AutoResetEvent类,对自动化程序进行同步。
AutoResetEvent类还有带参数的WaitOne()方法,用于指定最大等待时间,超过就不再等待:
//9000毫秒,true标识可以再等待结束之前推出同步域 documentComplete.WaitOne(9000,true)
操纵并检查IE Shell
1 static void Main(string[] args) 2 { 3 SHDocVw.InternetExplorer ie = null; 4 //把ie对象连接到IE程序所在的进行 5 6 Console.WriteLine("Setting IE to size 450x360"); 7 ie.Width = 450; 8 ie.Height = 360; 9 Thread.Sleep(1000); 10 11 if (ie.StatusText.IndexOf("Done") == -1) 12 Console.WriteLine("could not find 'Done' in status bar"); 13 else 14 Console.WriteLine("Find 'Done' in status bar"); 15 //... 16 }
当编写针对Web UI的自动化测试程序师,需要把IE的3个区域考虑在内:
- 客户区域,即待测页面所在区域;
- Shell区域,即诸如地址栏和回退按钮等IE控件所在的区域;
- 以及其他窗口,比如alert对话框等,这些窗口与IE是分开的。
SHDocVw.InternetExplorer对象提供了一些属性和方法用于操纵(可用来模拟用户操作)和检查Shell(可用来判断某个测试场景通过与否)。下面是几种属性和方法:
- GoBack(), GoForward(), GoHome(), Refresh(), Quit();
- Height, Width: 设置IE外壳的高度和宽度(以像素为单位)。
- Top, Left: 设置IE外壳左上角的位置(以像素为单位)。
- FullScreen:如果IE在全屏模式下运行,则返回true。
- MenuBar:如果IE菜单栏可见,则返回true。
- Resizable:如果可以调整IE的大小,则返回true。
- LocationURL:返回IE当前显示页面的URL。
- StatusText:返回IE状态栏的文本。
操作待测Web页面上的HTML元素的值
示例代码:
using mshtml; // .NET component = Microsoft.mshtml. HTML interfaces static void Main(string[] args) { //... HTMLDocument theDoc = (HTMLDocument)ie.Document; Console.WriteLine(" Selecting 'ID' radio button"); HTMLInputElement radioButton = (HTMLInputElement)theDoc.getElementById("RadioButtonList1_2"); radioButton.@checked = true; Console.WriteLine("Setting text box to '2B'"); HTMLInputElement textBox = (HTMLInputElement)theDoc.getElementById("TextBox1"); textBox.value = "2B"; Console.WriteLine("Clicking search button"); HTMLInputElement butt = (HTMLInputElement)theDoc.getElementById("Button1"); butt.click(); documentComplete.WaitOne(); //... }
在上述代码中,要模拟用户选中radio button控件,必须使用@checked,因为checked是C#语言的一个关键字。
在上述代码中,按钮控件和文本控件的类型都是HTMLInputElement,而下拉空间的类型则是HTMLSelectElement。可通过下面代码知道到底该用哪个类:
HTMLInputElement textBox = (HTMLInputElement)theDoc.getElementById("TextBox1"); Console.WriteLine("The textbox has type" + textBox.GetType().ToString());
注意:因为我们是通过getElementById()方法获得HTML元素/控件的引用的,所以这个控件必须要有ID attribute,从而可以唯一的表示这个控件。若Web页面是由Visual Studio .NET UI设计器创建的,那么所有控件都有一个ID attribute。但是如果Web程序是手工创建(比如通过Notepad),那么最好修改这个程序,给他们加上一个ID attribute。
验证Web页面上HTML元素
通过mshtml.dll库里的getElementByTagName()方法和item()方法得到你想要的特定的元素。然后可以通过InnerText属性取回HTML元素的实际值。
设想某个待测页面含有几个<P>元素,和一个ID为“div2”的<div>元素。下面的代码在<p>元素中查找“aloha”,在<div>元素中查找"adios":
1 Console.WriteLine("Seek 'aloha' in <p>[2]"); 2 Console.WriteLine("<p> type is:" + theDoc.getElementsByTagName("p").GetType().ToString()); 3 HTMLParaElement paraElement = (HTMLParaElement)theDoc.getElementsByTagName("p").item(1, null); 4 if (paraElement.innerText.ToString().IndexOf("aloha") >= 0) 5 Console.WriteLine("Found target 'aloha'"); 6 else 7 Console.WriteLine("Target string not found"); 8 9 Console.WriteLine("Seek 'adios' in <div id='div2'>"); 10 HTMLDivElement divElement = (HTMLDivElement)theDoc.getElementsByTagName("div").item("div2", null); 11 if (divElement.innerText.ToString().IndexOf("adios") >= 0) 12 Console.WriteLine("Found target 'adios'"); 13 else 14 Console.WriteLine("Target string not found");
上述代码中,item()方法:
- 第一个参数可以为整数(整数时为从0开始的索引值)或字符串(字符串时为tag名字);
- 第二个参数是个索引值,但只有当item()返回得是一个集合时才用得到。
有时候这些元素的值并不属于任何子的HTML元素,可用以下代码解决:
1 // non-HTML element 2 Console.WriteLine("Seeking 'Search Complete' in body"); 3 HTMLBody body = (HTMLBody)theDoc.getElementsByTagName("body").item(0, null); 4 if (body.createTextRange().findText("Search Complete", 0, 4) == true) 5 { 6 Console.WriteLine("Found target string"); 7 } 8 else 9 { 10 Console.WriteLine("*Target string not found*"); 11 pass = false; 12 }
上述代码中,findText()方法:
- 第一个参数是必添的目标字符串
- 第二个参数用于制定查找起始位置
- 第二个参数用于指定查找类型
示例代码
1 // Chapter 7 - Low-Level Web UI Testing 2 // Example Program: LowLevelUITest 3 4 5 using System; 6 using SHDocVw; // COM component = Microsoft Internet Controls. IE object 7 using mshtml; // .NET component = Microsoft.mshtml. HTML interfaces 8 using System.Diagnostics; // Process 9 using System.Threading; // Sleep() 10 11 namespace RunTest 12 { 13 class Class1 14 { 15 static AutoResetEvent documentComplete = new AutoResetEvent(false); 16 17 [STAThread] 18 static void Main(string[] args) 19 { 20 try 21 { 22 Console.WriteLine(" Starting test run"); 23 24 bool pass = true; // assume test run will pass 25 26 SHDocVw.InternetExplorer ie = null; 27 Console.WriteLine(" Launching an instance of IE"); 28 Process p = Process.Start("iexplore.exe", "about:blank"); 29 System.Threading.Thread.Sleep(2000); 30 if (p == null) 31 throw new Exception("Could not launch IE"); 32 Console.WriteLine("Process handle = " + p.MainWindowHandle.ToString()); 33 34 SHDocVw.ShellWindows allBrowsers = new SHDocVw.ShellWindows(); 35 Console.WriteLine("Number active browsers = " + allBrowsers.Count); 36 37 if (allBrowsers.Count == 0) 38 throw new Exception("Cannot find IE"); 39 40 Console.WriteLine("Attaching to IE"); 41 int i = 0; 42 43 while (i < allBrowsers.Count && ie == null) 44 { 45 InternetExplorer e = (InternetExplorer)allBrowsers.Item(i); 46 if (e != null) 47 { 48 if (e.HWND == (int)p.MainWindowHandle) 49 ie = e; 50 } 51 ++i; 52 } 53 54 if (ie == null) 55 throw new Exception("Failed to attach to IE"); 56 57 ie.DocumentComplete += new DWebBrowserEvents2_DocumentCompleteEventHandler(ie_DocumentComplete); 58 59 Console.WriteLine(" Navigating to the Web app"); 60 object nil = new object(); 61 ie.Navigate("http://localhost:30614/WebAUT/Default.aspx", ref nil, ref nil, ref nil, ref nil); 62 63 documentComplete.WaitOne(); 64 65 Console.WriteLine("Setting IE to size 450x360"); 66 ie.Width = 450; 67 ie.Height = 360; 68 Thread.Sleep(1000); 69 70 //if (ie.StatusText.IndexOf("Done") == -1) 71 // Console.WriteLine("could not find 'Done' in status bar"); 72 //else 73 // Console.WriteLine("Find 'Done' in status bar"); 74 75 HTMLDocument theDoc = (HTMLDocument)ie.Document; 76 77 Console.WriteLine(" Selecting 'ID' radio button"); 78 HTMLInputElement radioButton = (HTMLInputElement)theDoc.getElementById("RadioButtonList1_2"); 79 radioButton.@checked = true; 80 81 82 Console.WriteLine("Setting text box to '2B'"); 83 HTMLInputElement textBox = (HTMLInputElement)theDoc.getElementById("TextBox1"); 84 Console.WriteLine("The textbox has type" + textBox.GetType().ToString()); 85 textBox.value = "2B"; 86 87 Console.WriteLine("Clicking search button"); 88 HTMLInputElement butt = (HTMLInputElement)theDoc.getElementById("Button1"); 89 butt.click(); 90 91 documentComplete.WaitOne(); 92 93 Console.WriteLine("Seek 'aloha' in <p>[2]"); 94 Console.WriteLine("<p> type is:" + theDoc.getElementsByTagName("p").GetType().ToString()); 95 HTMLParaElement paraElement = (HTMLParaElement)theDoc.getElementsByTagName("p").item(1, null); 96 if (paraElement.innerText.ToString().IndexOf("aloha") >= 0) 97 Console.WriteLine("Found target 'aloha'"); 98 else 99 Console.WriteLine("Target string not found"); 100 101 Console.WriteLine("Seek 'adios' in <div id='div2'>"); 102 HTMLDivElement divElement = (HTMLDivElement)theDoc.getElementsByTagName("div").item("div2", null); 103 if (divElement.innerText.ToString().IndexOf("adios") >= 0) 104 Console.WriteLine("Found target 'adios'"); 105 else 106 Console.WriteLine("Target string not found"); 107 108 // non-HTML element 109 Console.WriteLine("Seeking 'Search Complete' in body"); 110 HTMLBody body = (HTMLBody)theDoc.getElementsByTagName("body").item(0, null); 111 if (body.createTextRange().findText("Search Complete", 0, 4) == true) 112 { 113 Console.WriteLine("Found target string"); 114 } 115 else 116 { 117 Console.WriteLine("*Target string not found*"); 118 pass = false; 119 } 120 121 if (pass) 122 Console.WriteLine(" Test result = Pass "); 123 else 124 Console.WriteLine(" Test result = *FAIL* "); 125 126 Console.WriteLine("Closing IE in 4 seconds . . . "); 127 Thread.Sleep(4000); 128 ie.Quit(); 129 130 } 131 catch (Exception ex) 132 { 133 Console.WriteLine("Fatal error: " + ex.Message); 134 Console.ReadLine(); 135 } 136 137 } // Main() 138 139 private static void ie_DocumentComplete(object pDisp, ref object URL) 140 { 141 documentComplete.Set(); 142 } 143 144 } // class Class1 145 } // ns RunTest