[小北De编程手记] : Lesson 05 - Selenium For C# 之 API 下

  上一篇,我们介绍了一些Selenium WebDriver相关的API,下面我们就接着上一篇继续介绍Selenium常用的API,这一篇的内容主要涉及到以下话题:

  • Selenium API:复杂事件处理
  • Selenium API:特殊DOM元素处理
  • Selenium API:截图功能
  • Selenium API:关于框架扩展

(一)Selenium API:复杂事件处理

  首先,我们试想一下这样的场景。待测试的系统支持一些组合键的操作,例如:按住Ctrl的同时点击某个表格的某个单元格,该数据行会高亮显示。要模拟这样的操作,我们应该怎么处理呢?Selenium WebDriver的API为我们提供了一个Actions类,使得我们可以模拟用户复杂的操作。我先列出一个测试刚才描述场景的测试用例实现:

 1             //Step 01 : 启动浏览器并打开某个站点的首页。
 2             var driver = new FirefoxDriver();
 3             driver.Url = "http://www.xxx.com";
 4 
 5             //Step 02 : 寻找需要操作的表格单元格。
 6             var tableCol = driver.FindElement(By.XPath("//table[@id='tblTest']/tbody/tr/td[1]"));
 7             var action = new Actions(driver);
 8             action.KeyDown(Keys.Control);
 9             action.Click(tableCol);
10             action.Build();
11             action.Perform();

  上面代码的Step02展示了如何利用Actions对象构建复杂的操作,值得注意的是当调用Actions对象的操作方法(即Click和KeyDown)时并没有真正的执行你想要的操作,而是当Perform被调用的时候才真正的执行一些列的操作。另外,Actions类所有的操作方法都是自引用的。因此,我们可以用连续调用的方式更简洁的完成操作。(说明:自引用的概念是编程大师C++之父 “比亚尼·斯特朗斯特鲁普” 在《The C++ Programming Language》中最早提出的,就是一个对象的某个方法调用结束之后返回该对象本身的一种编程方式),因此之前的代码等价于:

1             //Step 01 : 启动浏览器并打开某个站点的首页。
2             var driver = new FirefoxDriver();
3             driver.Url = "http://www.xxx.com";
4 
5             //Step 02 : 寻找需要操作的表格单元格。
6             var tableCol = driver.FindElement(By.XPath("//table[@id='tblTest']/tbody/tr/td[1]"));
7             var action = new Actions(driver);
8             action.KeyDown(Keys.Control).Click(tableCol).Build().Perform();

下面我简单的罗列一下Actions类常用的方法:

  • Click : 点击元素。
  • ClickAndHold : 单击元素并保持按下的状态。
  • Release : 在元素上释放鼠标。
  • ContextClick : 单击鼠标右键。
  • DoubleClick : 双击元素。
  • MoveByOffset : 移动鼠标到特定位置或某个元素。
  • KeyDown : 按下键盘的某个按键。
  • KeyUp: 释放键盘的某个按键。
  • SendKeys : 向元素输入指定文本。
  • DragAndDrop : 拖拽元素。
  • DragAndDropToOffset : 拖拽元素到指定位置。
  • Build : 构建操作的链表。
  • Perform : 执行当前Built 的 Action。(即执行用户定义的一系列操作)

这些函数的声明如下:

  关于上述操作,还是照例给大家做了个Demo。打开博客园首页实现选中“代码改变世界”的效果,选中本身意义不大,这里只是展示一下Selenium的操作粒度可以细到一个怎么样的级别。代码如下:

 1         /// <summary>
 2         /// demo4 : 复杂交互
 3         /// </summary>
 4         [Fact(DisplayName = "Cnblogs.SeleniumAPI.Demo4")]
 5         public void SeleniumAPI_Demo4()
 6         {
 7             _output.WriteLine("Step 01 : 启动浏览器并打开博客园首页。");
 8             IWebDriver driver = new FirefoxDriver();
 9             driver.Url = "http://www.cnblogs.com";
10 
11             _output.WriteLine("Step 02 : 寻找需要操作的页面元素。");
12             var divText = driver.FindElement(By.Id("site_nav_top"));            
13             var point = divText.Location;
14             var width = int.Parse(divText.GetCssValue("width").Replace("px", string.Empty));
15             
16             _output.WriteLine("Step 03 : 选中文本信息。");
17             var action = new Actions(driver);
18             action
19                 .MoveByOffset(point.X, point.Y)         //移动鼠标到文本开头
20                 .ClickAndHold()                         //按下鼠标    
21                 .MoveByOffset(point.X + width, point.Y) //移动鼠标到文本结束
22                 .Release()                              //释放鼠标
23                 .Build()
24                 .Perform();
25 
26             System.Threading.Thread.Sleep(5000);
27             _output.WriteLine("Step 04 : 关闭浏览器。");
28             driver.Close();
29         }

 运行结果如下图所示,我们可以看到首页上的文字已经被选中:

(二)Selenium API:特殊DOM元素处理

  对于常见的按钮,输入框,超链接等元素,之前的Demo已经有很多相关的演示了,大多数对DOM元素的操作也都是点击,输入文本,获取值... ... 这些操作IWebElement中都有相关的定义,我之前的文章《Lesson 02 - Selenium For C# 之 核心对象》里有相关的介绍,这里就不再赘述了。这一部分,我们主要介绍一下特殊的DOM元素以及处理方式。

@.下拉菜单处理

  下拉菜单是一种很常见的DOM元素,它不同于文本框、按钮这样的元素(一个标签就可以描述)。而是由多个标签组成的,其结构如下图所示:

  那么在Selenium中我们应该如何处理呢?针对Select标签,Selenium提供了一个专门的类来处理。声明如下:

 1     // Summary:
 2     //     Provides a convenience method for manipulating selections of options in an
 3     //     HTML select element.
 4     public class SelectElement : IWrapsElement
 5     {
 6         // Summary:
 7         //     Gets all of the selected options within the select element.
 8         public IList<IWebElement> AllSelectedOptions { get; }
 9         //
10         // Summary:
11         //     Gets a value indicating whether the parent element supports multiple selections.
12         public bool IsMultiple { get; }
13         //
14         // Summary:
15         //     Gets the list of options for the select element.
16         public IList<IWebElement> Options { get; }
17 
18         public IWebElement SelectedOption { get; }
19 
20         public void DeselectAll();
21 
22         public void DeselectByIndex(int index);
23 
24         public void DeselectByText(string text);
25 
26         public void DeselectByValue(string value);
27 
28         public void SelectByIndex(int index);
29 
30         public void SelectByText(string text);
31 
32         public void SelectByValue(string value);
33     }

  SelectElement常见的操作方法和属性如下:

  • IsMultiple : 获取DOM对象是不是支持多选。
  • Options : 获取所有的Option对象。
  • SelectedOption : 获取选中的Option对象。
  • SelectByIndex : 根据索引选中元素。
  • SelectByText : 根据文本内容选中元素。
  • SelectByValue : 根据Value属性的值选中元素。
  • DeselectXXX : Deselect开头的方法都是用来撤销选中的。

  实在是找不的一个下拉菜单,so... ...只能简单的写一下如何使用了,下面的Demo中展示了如何转化并得到SelectElement对象并进行相关的操作。还有一点值得说明的是xUnit.Net中当设置Fact标签的Skip属性时,这个case在运行时是会被Runner忽略掉的,代码如下:

 1         /// <summary>
 2         /// demo5 : SelectElement
 3         /// </summary>
 4         [Fact(DisplayName = "Cnblogs.SeleniumAPI.Demo5", Skip = "Just Demo")]
 5         public void SeleniumAPI_Demo5()
 6         {
 7             _output.WriteLine("Step 01 : 启动浏览器并打开某个网站。");
 8             IWebDriver driver = new FirefoxDriver();
 9             driver.Url = "http://www.xxx.com";
10 
11             _output.WriteLine("Step 02 : 寻找需要操作的页面元素。");
12             var dllDom = (SelectElement)driver.FindElement(By.Id("selectDomId"));
13             var isMultiple = dllDom.IsMultiple;
14             var option = dllDom.SelectedOption;
15             dllDom.SelectByIndex(1);
16             dllDom.SelectByText("Text");
17             dllDom.SelectByValue("Value");
18             
19             _output.WriteLine("Step 03 : 关闭浏览器。");
20             driver.Close();
21         }

@.单选按钮的处理

  这里需要说明的另一种DOM元素是单选按钮,其实在处理单选按钮的时候和其他的IWebElement元素是一致的。都是使用IWebElement定义的Click方法点击,用Selected属性获取元素的选中状态。那为什么我要把这个元素单独提出讲解呢?原因就是单选按钮往往是一组只能有一个被选中。对于这样的元素状态我们要特别注意。如:页面上有A,B,C三个单选按钮,默认A会被选中,此时你执行下面的代码就可能会引发异常(Selenium会告知你A元素状态已经改变需要重新加载)。

1             var radioA = driver.FindElement(By.Id("radioA_Id"));
2             var radioB = driver.FindElement(By.Id("radioB_Id"));
3             radioB.Click();
4             var isSelected = radioA.Selected;  // 抛出异常:元素已经改变

  其实,Selenium中我们经常需要处理某个已经缓存元素发生变化的情况。至于相关的最佳实践,我会在后续的文章中介绍,本文主要是讲解一些基础的操作。

  对于不熟悉HTML的同学(说明一下,做B/S自动化 HTML是必须会的,so....还是要好好了解一下的),在此附上单选按钮的DOM结构实例:

(三)Selenium API:截图功能

  企业级的测试框架大多都会在测试CASE失败的时候对当前的UI进行截图,以方便测试人员定位Test Case失败的原因。这里我就来专门介绍一下Selenium 提供的截图功能,之前我们提到过我们使用的WebDriver继承自RemoteWebDriver。而RemoteWebDriver实现的很多的功能接口,比如之前我们所提到的IJavaScriptExecutor接口。ITakesScreenshot接口定义了获取屏幕截图方法,同样RemoteWebDriver实现了这个接口,也就是说我们可以利用WebDriver对象来获取屏幕的截图。ITakesScreenshot的定义如下:

 1 namespace OpenQA.Selenium
 2 {
 3     // Summary:
 4     //     Defines the interface used to take screen shot images of the screen.
 5     public interface ITakesScreenshot
 6     {
 7         // Summary:
 8         //     Gets a OpenQA.Selenium.Screenshot object representing the image of the page
 9         //     on the screen.
10         //
11         // Returns:
12         //     A OpenQA.Selenium.Screenshot object containing the image.
13         Screenshot GetScreenshot();
14     }
15 }

  下面我们就来看看如何实现截图的功能,我们重写了之前的一个Demo,打开博客园首页搜索“小北De编程手记”。这一次,添加了截图查询结果页面的功能,并把图片保存到了当前的执行目录下,使用ITakesScreenshot.GetScreenshot获取Selenium提供的截屏对象,并保存截屏图片到本地文件

 1         /// <summary>
 2         /// demo6 : 截屏
 3         /// </summary>
 4         [Fact(DisplayName = "Cnblogs.SeleniumAPI.Demo6")]
 5         public void SeleniumAPI_Demo6()
 6         {
 7             _output.WriteLine("Step 01 : 启动浏览器并打开博客园首页。");
 8             IWebDriver driver = new FirefoxDriver();
 9             driver.Url = "http://www.cnblogs.com";
10 
11             _output.WriteLine("Step 02 : 寻找需要操作的页面元素。");
12             var txtSearch = driver.FindElement(By.Id("zzk_q"));
13             var btnSearch = driver.FindElement(By.XPath(".//input[@type='button' and @value='找找看']"));
14 
15             _output.WriteLine("Step 03 : 输入查询文本&点击查询");
16             txtSearch.SendKeys("小北De编程手记");
17             btnSearch.Click();
18             
19             _output.WriteLine("Step 04 : 截屏");
20             var takesScreenshot = (ITakesScreenshot)driver;
21             var screenshot = takesScreenshot.GetScreenshot();
22             screenshot.SaveAsFile("screenshot.png", ImageFormat.Png);
23 
24             _output.WriteLine("Step 05 : 关闭浏览器。");
25             driver.Close();
26         }

代码运行完毕之后,查看本地目录,可以找到刚刚保存的文件:

 

(四)Selenium API:框架功能扩展

  最后,还是要给大家提一下关于如何扩展Selenium API的功能,这个部分我会在其他的关于自动化测试框架构建的系列中详细描述,在这里只是简单的提一下框架预留的接口IJavaScriptExecutor,之前的文章《Lesson 03 - Selenium For C# 之 元素定位》对这个接口有一个简要的介绍。这里再次提及,主要有两个原因:

  第一,该接口可以很好的发挥Javascript的能力,如果结合C#本身的功能(当然你也可以用Java,Ruby等其他Selenium支持的编程语言)可以帮助让你的自动化测试框架具有很多很酷的功能(例如:控制浏览器滚动条,更简单的读取表格元素... ...)。

  第二,Selenium有着和优雅的设计,WebDriver还实现了许许多多其他的接口,IJavaScriptExecutor ITakesScreenshot也只是其中的一部分。每个接口都有特定的能力。这些往往是一般的自动化测试用例编写人员不需要关心的地方,但如果你是一个测试框架的架构师或是一个打算构建自己的测试框架的小伙伴。那么了解这些接口是你的必经之路。

 

关于《Selenium For C#》 系列,我计划给大家逐一介绍一些Selenium Driver的基础知识和框架的扩展点。 当然,之后会有更多关于测试框架构以及软件构建方面的文章。愿我主保佑我有时间做完这件事情... ...

《Selenium For C#》的相关文章:Click here.

说明:Demo地址:https://github.com/DemoCnblogs/Selenium

 

如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
作者:小北@North
来源:http://www.cnblogs.com/NorthAlan
声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权,贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。

 

posted @ 2016-01-30 23:58  小北@Alan  阅读(3899)  评论(3编辑  收藏  举报