目录
WebDriver拾级而上·之十一 在selenium2.0中使用selenium1.0的API
WebDriver拾级而上·之十二 利用selenium-webdriver截图
WebDriver拾级而上·之十三 调用Java Script
WebDriver拾级而上·之十四 RemoteWebDriver
TestNG·九 使用testng-xslt改写testng的测试报告
RubyWebDriver·G处理弹出窗alert和confirm
WebDriver(JAVA)拾级而上
WebDriver拾级而上·之零 WebDriver理论
Selenium2.0 = Selenium1.0 + WebDriver(也就是说Selenium2.0合并了这两个项目)
Selenium1.0可以使用任何编程语言,但是有个先决条件就是必须支持HTTP库。Selenium1.0起初就是一个Javascript库,到后面引入了SeleniumRC。SeleniumRC作为一个代理服务器并且发送操作命令给Selenium Core(javascript代码,且为SeleniumRC的一部分)。SeleniumRC从测试程序接收指令并翻译,返回测试结果给测试程序。Selenium Core在client API打开浏览器后就注入到浏览器中,然后Selenium Core接收测试程序的指令,解释成selenese命令,在浏览器执行。
selenium-webdriver实现原理(这里以webdriver java binding的firefox-webdriver实现为例),简单介绍一下webdriver的工作原理。
1.当测试脚本启动firefox的时候,selenium-webdriver 会首先在新线程中启动firefox浏览器。如果测试脚本指定了firefox的profile,那么就以该profile启动,否则的话就新启1个profile,并启动firefox;
2.firefox一般是以-no-remote的方法启动,启动后selenium-webdriver会将firefox绑定到特定的端口,绑定完成后该firefox实例便作为webdriver的remote server存在;
3.客户端(也就是测试脚本)创建1个session,在该session中通过http请求向remote server发送restful的请求,remote server解析请求,完成相应操作并返回response;
4.客户端接受response,并分析其返回值以决定是转到第3步还是结束脚本;
这就是webdriver的工作流程,看起来很复杂,实际上当了解了webdriver的实现原理后,理解上述问题应该比较简单。
webdriver是按照server–client的经典设计模式设计的。
server端就是remote server,可以是任意的浏览器。当我们的脚本启动浏览器后,该浏览器就是remote server,它的职责就是等待client发送请求并做出相应的操作;
client端简单说来就是我们的测试代码,我们测试代码中的一些行为,比如打开浏览器,转跳到特定的url等操作是以http请求的方式发送给被测试浏览器,也就是remote server;remote server接受请求,并执行相应操作,并在response中返回执行状态、返回值等信息;
举个实际的例子,下面代码的作用是”命令”firefox转跳到google主页:
WebDriver diver = new FirefoxDriver();
driver.get("http://google.com");
在执行 driver.get("http://google.com"); 这句代码时,client也就是我们的测试代码向remote server发送了如下的请求:
POST session/285b12e4-2b8a-4fe6-90e1-c35cba245956/url
post_data {"url":"http://google.com"}
通过post的方式请求localhost:port/hub/session/session_id/url地址,请求浏览器完成跳转url的操作。
如果上述请求是可接受的,或者说remote server是实现了这个接口,那么remote server会跳转到该post data包含的url,并返回如下的response
{"name":"get","sessionId":"285b12e4-2b8a-4fe6-90e1-c35cba245956","status":0,"value":""}
该response中包含如下信息
name:remote server端的实现的方法的名称,这里是get,表示跳转到指定url;
sessionId:当前session的id;
status:请求执行的状态码,非0表示未正确执行,这里是0,表示一切ok不必担心;
value:请求的返回值,这里返回值为空,如果client调用title接口,则该值应该是当前页面的title;
如果client发送的请求是定位某个特定的页面元素,则response的返回值可能是这样的:
{"name":"findElement","sessionId":"285b12e4-2b8a-4fe6-90e1-c35cba245956","status":0,"value":{"ELEMENT":"{2192893e-f260-44c4-bdf6-7aad3c919739}"}}
name,sessionId,status跟上面的例子是差不多的,区别是该请求的返回值是ELEMENT:{2192893e-f260-44c4-bdf6-7aad3c919739},表示定位到元素的id,通过该id,client可以发送如click之类的请求与server端进行交互。
那么remote server端的这些功能是如何实现的呢?答案是浏览器实现了webdriver的统一接口,这样client就可以通过统一的restful的接口去进行浏览器的自动化操作。目前webdriver支持ie, chrome, firefox, opera等主流浏览器,其主要原因是这些浏览器实现了webdriver约定的各种接口。
WebDriver拾级而上·之一 环境部署
1.下载安装eclipse和jdk
2.下载最新的Selenium Client Drivers
http://seleniumhq.org/download/
3.在eclipse中建立项目中导入所下载的包。(如果selenium-java-2.21.0.jar导入后,运行报错,则把下载的selenium-java-2.21.0.jar包中同一级目录下的libs中的jar包全部导入)
4.配置testng
4.1Eclipse中点击Help->Install new software -> 点击Add
4.2在Location输入 http://beust.com/eclipse
4.3选中Testng版本,点击Next,按照提示安装,安装完之后重启Eclipse
4.4新建JavaProject,右键BuildPath,添加testng.jar
4.5新建一个sum类,用来计算两整数之和,代码如下:
package com.hpp;
public class sum {
private int no1;private int no2;
private int mysum;
public int add(int no1,int no2){
mysum=no1+no2;return mysum;
}
}
4.6再新建testng class
4.7点击finish,代码如下
package com.test;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import com.hpp.sum;
public class NewTest {
private sum newSum=new sum();
@Test
public void f() {
int mysum=newSum.add(1, 2);
assertEquals(3,mysum,"Right");
}
}
testing,xml会自动配置好的,这里不用管项目的文件结构如下:
4.8在testing.xml右键点击RunAs->Testng Suite,即可看到结果
如果想要换个测试用例,可以修改文件testing.xml中class标签name的值
4.9也可以不用在 Testng Suite 模式下运行
新建class,代码如下
run as->Java Application 即可。
注:如果是用chrome浏览器运行脚本,需要下载最新的chromedriver.exe,放在目录C:\WINDOWS\system32 下即可。
下载地址: http://chromedriver.storage.googleapis.com/index.html
package com.test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
public class Test_google {
public static void main(String[] args) {
String url = "http://www.google.com.hk";
//String url = "http://mobile.service.com/user/CheckLogin.aspx?UserName=aaa@aa.aa&Password=123456&key=889";
//System.setProperty("webdriver.firefox.bin","D:\\Program Files\\Mozilla Firefox\\firefox.exe");
//WebDriver driver = new FirefoxDriver();
//打开ie WebDriver ie_driver = new InternetExplorerDriver();
//打开chrome
WebDriver driver = new ChromeDriver();
driver.get(url);
WebElement element = driver.findElement(By.name("q"));
element.sendKeys("hello Selenium!");
element.submit();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("页面Title:"+driver.getTitle()+"\n页面URL:"+driver.getCurrentUrl());
//System.out.println("返回当前的浏览器的窗口句柄:"+driver.getWindowHandle());
//String s=driver.getPageSource();s=s.substring(s.indexOf("{"), s.indexOf("}"));
//System.out.println("当前页面的源码:"+s);
driver.quit();
}
}
4.10 如果要在Myeclipse安装插件Testng
直接把eclipse里的org.testng文件夹拷贝到dropins目录
重启Myeclipse会提示如下,确定就OK了
5.Eclipse中导入jar文件的源码
在eclipse中导入jar文件对应的源码,主要是为了方便查看一些接口的源码,可以直接按着Ctrl+鼠标左键跳到对应的源码文件。
若无法查看类,函数等信息
将下载selenium-java-2.21.0包中的selenium-java-2.21.0-srcs.jar导入
WebDriver拾级而上·之二 浏览器操作
1.启动浏览器
A.firefox
//打开默认路径的firefox(路径指的是 firefox 的安装路径)
WebDriver diver = new FirefoxDriver();
//打开指定路径的firefox,方法1
System.setProperty("webdriver.firefox.bin","D:\\ProgramFiles\\Mozilla Firefox\\firefox.exe");
WebDriver dr = new FirefoxDriver();
//打开指定路径的firefox,方法2
File pathToFirefoxBinary = new File("D:\\Program Files\\Mozilla Firefox\\firefox.exe");
FirefoxBinary firefoxbin = new FirefoxBinary(pathToFirefoxBinary);
WebDriver driver1 = new FirefoxDriver(firefoxbin,null);
B.ie
//打开ie
WebDriver ie_driver = new InternetExplorerDriver();
C.chrome
因为Chrome Driver是Chromium 项目自己支持和维护的,所以你必需另外下载chromedriver.exe,放在目录下C:\WINDOWS\system32
下载地址:
http://code.google.com/p/chromedriver/downloads/list
http://chromedriver.storage.googleapis.com/index.html
//打开chrome
WebDriver driver = new ChromeDriver();
另一种启动chrome 的方法
wiki介绍:http://code.google.com/p/selenium/wiki/ChromeDriver
//打开chrome
System.setProperty("webdriver.chrome.driver", "D:\\chromedriver.exe");
System.setProperty("webdriver.chrome.bin",
C:\\Documents and Settings\\fy\\Local Settings"
+"\\Application Data\\Google\\Chrome\\Application\\chrome.exe");
Chromium介绍:http://code.google.com/p/chromium/
2.页面跳转url
String url = "http://www.baidu.com";
WebDriver driver = new FirefoxDriver();
A//用get方法
driver.get(url);
B//用navigate方法,然后再调用to方法,chrome不支持这种方法
driver.navigate().to(url);
3.如果页面文件在本地可以这么写
WebDriver dr = new ChromeDriver();
//页面文件在项目src下的路径 src/filename.html
File file = new File("src/filename.html");
String filePath = "file:///" + file.getAbsolutePath();
System.out.printf("now accesss %s \n", filePath);
dr.get(filePath);
4.关闭浏览器
//quit 关闭所有页面 close 关闭本次执行打开的页面
A.//用quit方法
driver.quit();
B.//用close方法
driver.close();
5.浏览器最大化
driver.manage().window().maximize();
6.获取页面信息
//得到title
String title = driver.getTitle();
//得到当前页面url
String currentUrl = driver.getCurrentUrl();
getWindowHandle() 返回当前的浏览器的窗口句柄
getWindowHandles() 返回当前的浏览器的所有窗口句柄
getPageSource() 返回当前页面的源码
//String s=driver.getPageSource();s=s.substring(s.indexOf("{"), s.indexOf("}"));
//System.out.println("当前页面的源码:"+s);
7.总结
操作浏览器的主要方法都来自org.openqa.selenium.WebDriver这个接口中。
源代码这些方法都是在 org.openqa.selenium.remote.RemoteWebDriver这个类中实现的,然后不同浏览的driver类继承 RemoteWebDriver。
WebDriver拾级而上·之三 定位页面元素
selenium-webdriver提供了强大的元素定位方法,支持以下三种方法:
单个对象的定位方法
多个对象的定位方法
层级定位
注意:
selenium-webdriver通过findElement()\findElements()等find方法调用"By"对象来定位和查询元素。By类只是提供查询的方式进行分类。findElement返回一个元素对象否则抛出异常,findElements返回符合条件的元素 List,如果不存在符合条件的就返回一个空的list。
一、定位单个元素
A.使用className进行定位
当所定位的元素具有class属性的时候我们可以通过classname来定位该元素。
例:下面的例子定位页面上class为"username"的li。
WebElement element = driver.findElement(By.className("username"));
System.out.println(element.getTagName());
输出结果:Li
B.使用id属性定位
例:<input id="passport_user" type="text" value="" title="用户名/邮箱" name="passport_user">
WebElement element = dr.findElement(By.id("passport_user"));
System.out.println(element.getAttribute("title"));
输出结果:用户名/邮箱
C.使用name属性定位
例:<input id="passport_user" type="text" value="" title="用户名/邮箱" name="passport_user">
WebElement e = dr.findElement(By.name("passport_user"));
D.使用css属性定位
例:<input id="passport_user" type="text" value="" title="用户名/邮箱" name="passport_user">
WebElement e1 = dr.findElement(By.cssSelector("#passport_user"));
详解:
1.css之后代选择器
<p>
<em>location1</em>
</p>
<ol>
<li><em>location2</em></li>
</ol>
可以通过css=p em这个可以选中文本为location1的em元素
css=ol em这个可以选中文本为location2的em元素
css后代选择器和xpath中//div//a一样:取得所有div下面所有的a节点。这个是忽略了父子节点
<div>
<p><em>location1</em></p>
</div>
<div>
<ol>
<li><strong><em>location2</em></strong></li>
<li><em>location3</em></li>
<li><em>location4</em></li>
</ol>
</div>
可以通过css=p>em来定位location1
css之父子节点选择器给后代选择器加了一个限制,类似xpath中//div/p/em:所有div下的子元素p的子元素em。
css=li+li em来定位location3,location4的em
css=li+strong+em来定位文本为location2的em
2.css之id选择器
<input id="location1" type="button"/>
<input id="location2" type="radio"/>
通过css=#location1来定位type为button的按钮
通过css=#location2来定位type为radio的单选框
3.css之类选择器
<input class="location1" type="button" value="确定"/>
<input class="location2" type="button" value="取消"/>
通过css=input.location1来选择value值为确定的按钮
通过css=input.location2来选择value值为取消的按钮
4.css之属性选择器
<input class="location1" type="button" value="确定"/>
<input class="location2" type="button" />
通过css=[class=location1]可以定位第一个按钮
通过css=[class~=1]可以定位第一个按钮
通过css=[value="确定"]可以定位第一个按钮
通过css=input[class="location"]可以定位第二个按钮
E.按标记(tag)名称查找
元素的DOM标记名称
<iframe src="..."></iframe>
WebElement frame = driver.findElement(By.tagName("iframe"));
F.按链接文本查找
<a href="http://www.google.com/search?q=cheese">cheese</a>>
WebElement cheese = driver.findElement(By.linkText("cheese"));
按部分链接文本查找
<a href="http://www.google.com/search?q=cheese">search for cheese</a>>
WebElement cheese = driver.findElement(By.partialLinkText("cheese"));
G.使用 XPATH定位
例:<input id="passport_user" type="text" value="" title="用户名/邮箱" name="passport_user">
WebElement element =dr.findElement(By.xpath("//input[@id='passport_user']"));
parent::返回父节点
following::返回此节点后面的兄弟节点
preceding::返回此节点前面的兄弟节点
div>
<input id="location1" type="button" />
<input type="button" />
</div>
通过id为location1可以很随意的定位到兄弟节点
//div/input[@id='location1']/following::input
也可以通过location1很随意的定位到父类节点div。
//div/input[@id='location1']/parent::div。
也可以通过索引为2的input定位到id为location1的input
//div/input[2]/preceding-sibling::input
有几个非常有用的Firefox插件,有助于发现一个元素的XPath:
XPath Checker - suggests XPath and can be used to test XPath results.
Firebug - XPath suggestions are just one of the many powerful features of this very useful add-on.
XPath Checker - 建议XPath并可以用于测试XPath的结果
Firebug - XPath建议仅仅是这非常有用的插件的许多强有力特征中的一个。
参考网站:http://www.w3school.com.cn/xpath/index.asp
二、定位多个元素
//定位到所有<input>标签的元素,然后输出他们的id
List<WebElement> element = driver.findElements(By.tagName("input"));
for (WebElement e : element)
System.out.println(e.getAttribute("id"));
三、层级定位
层级定位的思想是先定位父元素,然后再从父元素中精确定位出其我们需要选取的子元素。
层级定位一般的应用场景是无法直接定位到需要选取的元素,但是其父元素比较容易定位,通过定位父元素再遍历其子元素选择需要的目标元素,或者需要定位某个元素下所有的子元素。
下面的代码演示了如何使用层级定位class为"login"的div,然后再取得它下面的所有label,并打印出他们的文本
//定位class为"login"的div,然后再取得它下面的所有label,并打印出他们的值
WebElement element = driver.findElement(By.className("login"));
List<WebElement> el = element.findElements(By.tagName("label"));
for(WebElement e : el)
System.out.println(e.getText());
四、使用Javascript
你可以执行任何Javascript,以找到一个元素,只要你找到一个DOM元素,它将自动转换为WebElement对
象。
WebElement element = (WebElement) ((JavascriptExecutor)driver).executeScript("return
$('.cheese')[0]");
WebDriver拾级而上·之四 操作页面元素
一、输入框(text field or textarea)
//找到输入框元素:
WebElement element = driver.findElement(By.id("passwd-id"));
//将输入框清空:
element.clear();
//在输入框中输入内容:
element.sendKeys(“test”);
//获取输入框的文本内容:
element.getText();
二、下拉选择框(Select)
//找到下拉选择框的元素:
Select select = new Select(driver.findElement(By.id("select")));
//选择对应的选择项:
select.selectByVisibleText(“mediaAgencyA”);
或
select.selectByValue(“MA_ID_001”);
//不选择对应的选择项:
select.deselectAll();
select.deselectByValue(“MA_ID_001”);
select.deselectByVisibleText(“mediaAgencyA”);
或者获取选择项的值:
select.getAllSelectedOptions();
select.getFirstSelectedOption();
对下拉框进行操作时首先要定位到这个下拉框,new 一个Selcet对象,然后对它进行操作
例如:
以http://passport.51.com/reg2.5p这个页面为例。这个页面中有4个下拉框,下面演示4种选中下拉框选项的方法。
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.Select;
public class SelectsStudy {
public static void main(String[] args) {
System.setProperty("webdriver.firefox.bin","D:\\Program Files\\Mozilla Firefox\\firefox.exe");
WebDriver dr = new FirefoxDriver();
dr.get("http://passport.51.com/reg2.5p");
//通过下拉列表中选项的索引选中第二项,即2011年
Select selectAge = new Select(dr.findElement(By.id("User_Age")));
selectAge.selectByIndex(2);//Select.selectByIndex
//通过下拉列表中的选项的value属性选中"上海"这一项
Select selectShen = new Select(dr.findElement(By.id("User_Shen")));
selectShen.selectByValue("上海");//Select.selectByValue
//通过下拉列表中选项的可见文本选 中"浦东"这一项
Select selectTown = new Select(dr.findElement(By.id("User_Town")));
selectTown.selectByVisibleText("浦东");Select.selectByVisibleText
//这里只是想遍历一下下拉列表所有选项,用click进行选中选项
Select selectCity = new Select(dr.findElement(By.id("User_City")));
for(WebElement e : selectCity.getOptions())//Select.getOptions()
e.click();
}
}
三、单选项(Radio Button)
//找到单选框元素:
WebElement bookMode =driver.findElement(By.id("BookMode"));
//选择某个单选项:
bookMode.click();
//清空某个单选项:
bookMode.clear();
//判断某个单选项是否已经被选择:
bookMode.isSelected();
四、多选项(checkbox)
//多选项的操作和单选的差不多:
WebElement checkbox =driver.findElement(By.id("myCheckbox."));
checkbox.click();
checkbox.clear();
checkbox.isSelected();
checkbox.isEnabled();
五、按钮(button)
//找到按钮元素:
WebElement saveButton = driver.findElement(By.id("save"));
//点击按钮:
saveButton.click();
//判断按钮是否enable:
saveButton.isEnabled ();
六、左右选择框
也就是左边是可供选择项,选择后移动到右边的框中,反之亦然。
例如:
Select lang = new Select(driver.findElement(By.id("languages")));
lang.selectByVisibleText(“English”);
WebElement addLanguage =driver.findElement(By.id("addButton"));
addLanguage.click();
七、弹出对话框(Popup dialogs)
Alert alert = driver.switchTo().alert();
alert.accept();
alert.dismiss();
alert.getText();
八、表单(Form)
Form中的元素的操作和其它的元素操作一样,对元素操作完成后对表单的提交可以:
WebElement approve = driver.findElement(By.id("approve"));
approve.click();
或
approve.submit();//只适合于表单的提交
九、上传文件 (Upload File)
上传文件的元素操作:
WebElement adFileUpload = driver.findElement(By.id("WAP-upload"));
String filePath = "C:\test\\uploadfile\\media_ads\\test.jpg";
adFileUpload.sendKeys(filePath);
十、拖拉(Drag andDrop)
WebElement element =driver.findElement(By.name("source"));
WebElement target = driver.findElement(By.name("target"));
(new Actions(driver)).dragAndDrop(element, target).perform();
例如:下面这个页面是一个演示拖放元素的页面,你可以把左右页面中的条目拖放到右边的div框中。
http://koyoz.com/demo/html/drag-drop/drag-drop.html
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.interactions.Actions;
public class DragAndDrop {
public static void main(String[] args) {
System.setProperty("webdriver.firefox.bin","D:\\Program Files\\Mozilla Firefox\\firefox.exe");
WebDriver dr = new FirefoxDriver();
dr.get("http://koyoz.com/demo/html/drag-drop/drag-drop.html");
//首先new出要拖入的页面元素对象和目标对象,然后进行拖入。
WebElement element = dr.findElement(By.id("item1"));
WebElement target = dr.findElement(By.id("drop"));
(new Actions(dr)).dragAndDrop(element, target).perform();
//利用循环把其它item也拖入
String id="item" ;
for(int i=2;i<=6;i++){
String item = id+i;
(new Actions(dr)).dragAndDrop(dr.findElement(By.id(item)), target).perform();
}
}
}
代码很简单,需要注意的是(new Actions(dr)).dragAndDrop(element, target).perform();这句话中,dragAndDrop(element, target)这个方法是定义了“点击element元素对象,然后保持住,直到拖到目标元素对象里面才松开”这一系列动作的Actions,如果你不调用perform()方法,这个Actions是不会执行的。
十一、导航 (Navigationand History)
//打开一个新的页面:
driver.navigate().to("http://www.example.com");
//通过历史导航返回原页面:
driver.navigate().forward();
driver.navigate().back();
十二、获取页面CSS属性
1.获取文字颜色
dr.findElement(By.id("tooltip")).getCssValue("color")
2.获取文字字号
dr.findElement(By.tagName("h3")).getCssValue("font")
WebDriver拾级而上·之五 iframe的处理
有时候我们在定位一个页面元素的时候发现一直定位不了,反复检查自己写的定位器没有任何问题,代码也没有任何问题。这时你就要看一下这个页面元素是否在一个iframe中,这可能就是找不到的原因之一。
如果你在一个default content中查找一个在iframe中的元素,那肯定是找不到的。反之你在一个iframe中查找另一个iframe元素或default content中的元素,那必然也定位不到。
selenium webdriver中提供了进入一个iframe的方法:
WebDriver org.openqa.selenium.WebDriver.TargetLocator.frame(String nameOrId)
也提供了一个返回default content的方法:
WebDriver org.openqa.selenium.WebDriver.TargetLocator.defaultContent()
这样使我们面对iframe时可以轻松应对。
switch_to方法会new 1个TargetLocator对象,使用该对象的frame方法可以将当前识别的”主体”移动到需要定位的frame上去。
以下面的html代码为例,我们看一下处现iframe。
这个2个页面放桌面
Html代码
main.html
<html>
<head>
<title>FrameTest</title>
</head>
<body>
<div id = "id1">this is a div!</div>
<iframe id = "frame" frameborder="0" scrolling="no" style="left:0;position:absolute;" src = "frame.html"></iframe>
</body>
</html>
frame.html
<html>
<head>
<title>this is a frame!</title>
</head>
<body>
<div id = "div2">this is a frame,too!</div>
<label>input:</label>
<input id = "input2" value='frame VALUE'>a frame</input>
</body>
</html>
Java代码
package com.test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class Test_frame {
public static void main(String[] args) {
String url = "file:///C:/Documents and Settings/fei yong/桌面/main.html";
//打开chrome
WebDriver dr = new ChromeDriver();
dr.get(url);
//在default content定位id="id1"的div
dr.findElement(By.id("id1"));
//此时,没有进入到id="frame"的frame中时,以下两句会报错
//dr.findElement(By.id("div1"));//报错
//dr.findElement(By.id("input1"));//报错
//进入id="frame"的frame中,定位id="div1"的div和id="input1"的输入框。
dr.switchTo().frame("frame");
dr.findElement(By.id("div2"));
dr.findElement(By.id("input2"));
System.out.println("div2.getTagName:"+dr.findElement(By.id("div2")).getTagName()+";");
System.out.println("input2.getTagName:"+dr.findElement(By.id("input2")).getTagName());
System.out.println("input2.getText:"+dr.findElement(By.id("input2")).getText());
//此时,没有跳出frame,如果定位default content中的元素也会报错。
//dr.findElement(By.id("id1"));//报错
//跳出frame,进入default content;重新定位id="id1"的div
dr.switchTo().defaultContent();
dr.findElement(By.id("id1"));
System.out.println("id1.getText:"+dr.findElement(By.id("id1")).getText());
dr.quit();
}
}
页面输出:
WebDriver拾级而上·之六 获得弹出窗
捕获或者说定位弹出窗口的关键在于获得弹出窗口的句柄。
在代码里,使用getWindowHandle方法来获取当前浏览器窗口的句柄,使用了getWindowHandles方法获取所有弹出的浏览器窗口的句柄,然后通过排除当前句柄的方法来得到新开窗口的句柄。
在获取新弹出窗口的句柄后,使用switchto.window(newwindow_handle)方法,将新窗口的句柄作为参数传入既可捕获到新窗口了。
如果想回到以前的窗口定位元素,那么再调用1次switchto.window方法,传入之前窗口的句柄既可达到目的。
Html代码
<span style="white-space: normal; background-color: #ffffff;">test.html</span>
<html>
<head><title>Test Popup Window</title></head>
<body>
<a id = "bd" href = "http://www.baidu.com/" target = "_blank">Let's go!</a>
</body>
</html>
下面的代码演示了如何去得到弹出的新窗口
Java代码
package com.test_webdriver;
import java.util.Iterator;
import java.util.Set;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class Test_popup {
public static void main(String[] args) {
String url = "file:///D:/selenium/html/popup.html";
//打开chrome
WebDriver dr = new ChromeDriver();
dr.get(url);
dr.findElement(By.id("bd")).click();
//得到当前窗口的句柄
String currentWindow = dr.getWindowHandle();
//得到所有窗口的句柄
Set<String> handles = dr.getWindowHandles();
Iterator<String> it = handles.iterator();
while(it.hasNext()){
if(currentWindow == it.next()) continue;
WebDriver window = dr.switchTo().window(it.next());
System.out.println("title,url = "+window.getTitle()+","+window.getCurrentUrl());
}
dr.close();//关闭当前页面
//dr.quit();//关闭全部页面
}
}
输出结果:
title,url = 百度一下,你就知道,http://www.baidu.com/
WebDriver拾级而上·之七 处理对话框
Html代码
t.html (放在桌面)
<html>
<head>
<title>Alert</title>
</head>
<body>
<input id = "alert" value = "alert" type = "button" onclick = "alert('欢迎!请按确认继续!');"/>
<input id = "confirm" value = "confirm" type = "button" onclick = "confirm('确定吗?');"/>
<input id = "prompt" value = "prompt" type = "button" onclick = "var name = prompt('请输入你的名字:','请输入你的名字'); document.write(name) "/>
</body>
</html>
以上html代码在页面上显示了三个按钮,点击他们分别弹出alert、confirm、prompt对话框。如果在prompt对话框中输入文字点击确定之后,将会刷新页面,显示出这些文字 。
selenium webdriver 处理这些弹层的代码如下:
Java代码
package com.test;
import java.util.List;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.Select;
public class Test_Dialogs {
public static void main(String[] args) {
String url = "file:///C:/Documents and Settings/fei yong/桌面/t.html";
//打开chrome
WebDriver driver = new ChromeDriver();
driver.get(url);
//点击第一个按钮,输出对话框上面的文字,然后叉掉
driver.findElement(By.id("alert")).click();
Alert alert = driver.switchTo().alert();
String text = alert.getText();
System.out.println("alert:"+text);
alert.dismiss();
//点击第二个按钮,输出对话框上面的文字,然后点击确认
driver.findElement(By.id("confirm")).click();
Alert confirm = driver.switchTo().alert();
String text1 = confirm.getText();
System.out.println("confirm:"+text1);
confirm.accept();
//点击第三个按钮,输入你的名字,然后点击确认,最后
driver.findElement(By.id("prompt")).click();
Alert prompt = driver.switchTo().alert();
String text2 = prompt.getText();
System.out.println("prompt:"+text2);
prompt.sendKeys("fei");
prompt.accept();
//driver.quit();
}
}
页面输出:
Started ChromeDriver
port=5650
version=19.0.1068.0
log=E:\android\selenium\test_wdng_java\chromedriver.log
alert:欢迎!请按确认继续!
confirm:确定吗?
prompt:请输入你的名字:
小结:
从以上代码可以看出dr.switchTo().alert();这句可以得到alert\confirm\prompt对话框的对象,然后运用其方法对它进行操作。对话框操作的主要方法有:
getText() 得到它的文本值
accept() 相当于点击它的"确认"
dismiss() 相当于点击"取消"或者叉掉对话框
sendKeys() 输入值,这个alert\confirm没有对话框就不能用了,不然会报错。
WebDriver拾级而上·之八 操作cookies
一个Cookies主要属性有:所在域、name、value、有效日期和路径
Java代码
import java.util.Set;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
public class CookiesStudy {
static void main(String[] args) {
System.setProperty("webdriver.firefox.bin","D:\\Program Files\\Mozilla Firefox\\firefox.exe");
WebDriver dr = new FirefoxDriver();
dr.get("http://www.51.com");
//增加一个name = "name",value="value"的cookie
Cookie cookie = new Cookie("name", "value");
dr.manage().addCookie(cookie);
//得到当前页面下所有的cookies,并且输出它们的所在域、name、value、有效日期和路径
Set<Cookie> cookies = dr.manage().getCookies();
System.out.println(String.format("Domain -> name -> value -> expiry -> path"));
for(Cookie c : cookies)
System.out.println(String.format("%s -> %s -> %s -> %s -> %s",
c.getDomain(), c.getName(), c.getValue(),c.getExpiry(),c.getPath()));
//删除cookie有三种方法
//第一种通过cookie的name
dr.manage().deleteCookieNamed("CookieName");
//第二种通过Cookie对象
dr.manage().deleteCookie(cookie);
//第三种全部删除
dr.manage().deleteAllCookies();
}
}
小结:
上面的代码首先在页面中增加了一个cookie,然后遍历页面的所有cookies,并输出他们的主要属性。最后就是三种删除cookie的方法。
WebDriver拾级而上·之九 等待页面元素加载
web的自动化测试中,我们经常会遇到这样一种情况:当我们的程序执行时需要页面某个元素,而此时这个元素还未加载完成,这时我们的程序就会报错。怎么办?等待。等待元素出现后再进行对这个元素的操作。
在selenium-webdriver中我们用两种方式进行等待:明确的等待和隐性的等待。
一、明确的等待
明确的等待是指在代码进行下一步操作之前等待某一个条件的发生。最不好的情况是使用Thread.sleep()去设置一段确认的时间去等待。但为 什么说最不好呢?因为一个元素的加载时间有长有短,你在设置sleep的时间之前要自己把握长短,太短容易超时,太长浪费时间。
selenium webdriver提供了一些方法帮助我们等待正好需要等待的时间。利用WebDriverWait类和ExpectedCondition接口就能实现这一点。
下面的html代码实现了这样的一种效果:点击click按钮5秒钟后,页面上会出现一个红色的div块。我们需要写一段自动化脚本去捕获这个出现的div,然后高亮之。
Html代码
wait.html 放在桌面
下面的代码实现了高亮动态生成的div块的功能:
Java代码
package com.test;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
public class Test_waitfor {
public static void main(String[] args) {
String url = "file:///C:/Documents and Settings/bestfei/桌面/wait.html";
//打开chrome
WebDriver dr = new ChromeDriver();
dr.get(url);
WebElement button_b = dr.findElement(By.id("b"));
button_b.click();
WebDriverWait wait = new WebDriverWait(dr,10);
wait.until(new ExpectedCondition(){
@Override
public WebElement apply(WebDriver d) {
return d.findElement(By.className("red_box"));
}
});
WebElement element = dr.findElement(By.cssSelector(".red_box"));
System.out.println("获取.red_box的背景颜色属性值:"+element.getCssValue("background-color"));
//在红色区域外面加黄框
((JavascriptExecutor)dr).executeScript("arguments[0].style.border = \"5px solid yellow\"",element);
dr.quit();
}
}
页面输出:
Started ChromeDriver
port=36071
version=19.0.1068.0
获取.red_box的背景颜色属性值:rgb(255, 0, 0)
上面的代码WebDriverWait类的构造方法接受了一个WebDriver对象和一个等待最长时间(10秒)。然后调用until方法,其中重写了 ExpectedCondition接口中的apply方法,让其返回一个WebElement,即加载完成的元素,然后点击。默认情况下,WebDriverWait每500毫秒调用一次ExpectedCondition,直到有成功的返回,当然如果超过设定的值还没有成功的返回,将抛出异常。
二、隐性等待
隐性等待是指当要查找元素,而这个元素没有马上出现时,告诉WebDriver查询Dom一定时间。默认值是0,但是设置之后,这个时间将在WebDriver对象实例整个生命周期都起作用。上面的代码就变成了这样:
Java代码
package com.test;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
public class Test_waitfor2 {
public static void main(String[] args) {
String url = "file:///C:/Documents and Settings/fei yong/桌面/wait.html";
//打开chrome
WebDriver dr = new ChromeDriver();
dr.get(url);
//设置10秒
dr.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
dr.findElement(By.id("b")).click();
WebElement element = dr.findElement(By.cssSelector(".red_box"));
System.out.println("获取.red_box的背景颜色属性值:"+element.getCssValue("background-color"));
//在红色区域外面加黄框
((JavascriptExecutor)dr).executeScript("arguments[0].style.border = \"5px solid yellow\"",element);
//dr.quit();
}
}
页面输出:
Started ChromeDriver
port=37023
version=19.0.1068.0
log=E:\android\selenium\test_wdng_java\chromedriver.log
获取.red_box的背景颜色属性值:rgb(255, 0, 0)
WebDriver拾级而上·之十 封装与重用
WebDriver对页面的操作,需要找到一个WebElement,然后再对其进行操作,比较繁琐:
// Find the text inputelement by its name
WebElement element = driver.findElement(By.name("q"));
// Enter something to search for
element.sendKeys("Cheese!");
我们可以考虑对这些基本的操作进行一个封装,简化操作。比如,封装代码:
protected void sendKeys(By by, String value){
driver.findElement(by).sendKeys(value);
}
那么,在测试用例可以这样简化调用:
sendKeys(By.name("q"),"Cheese!");
看,这就简洁多了。
类似的封装还有:
package com.drutt.mm.end2end.actions;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import com.drutt.mm.end2end.data.TestConstant;
public class WebDriverAction {
//protected WebDriverdriver;
protected RemoteWebDriverdriver;
protected WebDriverWaitdriverWait;
protected booleanisWebElementExist(By selector) {
try {
driver.findElement(selector);
return true;
} catch(NoSuchElementException e) {
return false;
}
}
protected StringgetWebText(By by) {
try {
return driver.findElement(by).getText();
} catch (NoSuchElementException e) {
return "Textnot existed!";
}
}
protected voidclickElementContainingText(By by, String text){
List<WebElement>elementList = driver.findElements(by);
for(WebElement e:elementList){
if(e.getText().contains(text)){
e.click();
break;
}
}
}
protected StringgetLinkUrlContainingText(By by, String text){
List<WebElement>subscribeButton = driver.findElements(by);
String url = null;
for(WebElement e:subscribeButton){
if(e.getText().contains(text)){
url =e.getAttribute("href");
break;
}
}
return url;
}
protected void click(Byby){
driver.findElement(by).click();
driver.manage().timeouts().implicitlyWait(TestConstant.WAIT_ELEMENT_TO_LOAD,TimeUnit.SECONDS);
}
protected StringgetLinkUrl(By by){
return driver.findElement(by).getAttribute("href");
}
protected void sendKeys(Byby, String value){
driver.findElement(by).sendKeys(value);
}
WebDriver拾级而上·之十一 在selenium2.0中使用selenium1.0的API
Selenium2.0中使用WeDriver API对页面进行操作,它最大的优点是不需要安装一个selenium server就可以运行,但是对页面进行操作不如selenium1.0的Selenium RC API那么方便。
Selenium2.0提供了使用Selenium RC API的方法:
// 我用火狐浏览器作为例子
WebDriver driver = new FirefoxDriver();
String baseUrl ="http://www.google.com";
Selenium selenium = new WebDriverBackedSelenium(driver, baseUrl);
// 执行selenium命令
selenium.open("http://www.google.com");
selenium.type("name=q", "cheese");
selenium.click("name=btnG");
WebDriver driverInstance = ((WebDriverBackedSelenium)selenium).getUnderlyingWebDriver();
selenium.stop();
分别使用WebDriver API和SeleniumRC API写了一个Login的脚本,很明显,后者的操作更加简单明了。
(1)WebDriver API写的Login脚本:
public void login() {
driver.switchTo().defaultContent();
driver.switchTo().frame("mainFrame");
WebElement eUsername= waitFindElement(By.id("username"));
eUsername.sendKeys(manager@ericsson.com);
WebElement ePassword= waitFindElement(By.id("password"));
ePassword.sendKeys(manager);
WebElementeLoginButton = waitFindElement(By.id("loginButton"));
eLoginButton.click();
}
(2)SeleniumRC API写的Login脚本:
public void login() {
selenium.selectFrame("relative=top");
selenium.selectFrame("mainFrame");
selenium.type("username","manager@ericsson.com");
selenium.type("password","manager");
selenium.click("loginButton");
}
WebDriver拾级而上·之十二 利用selenium-webdriver截图
--好的测试人员都会截得一手好图,就跟骨灰级宅男定会吟得一手好诗一般。
截取页面全图,不管页面有多长。
Java代码
package com.test;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class Test_ShotScreen {
public static void main(String[] args) throws IOException, InterruptedException{
String url = "http://www.51.com";
//打开chrome
WebDriver dr = new ChromeDriver();
dr.get(url);
//这里等待页面加载完成
Thread.sleep(5000);
//下面代码是得到截图并保存在D盘下
File screenShotFile = ((TakesScreenshot)dr).getScreenshotAs(OutputType.FILE);
//FileUtils.copyFile(screenShotFile, new File("D:/test.png"));
FileUtils.copyFile(screenShotFile, new File("D:\\AutoScreenCapture\\" + getCurrentDateTime()+ ".jpg"));
dr.quit();
}
public static String getCurrentDateTime(){
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd_HHmmss");//设置日期格式
//System.out.println(df.format(new Date()));
return df.format(new Date());
}
}
--方法
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
public void takeScreenShot(String name){
File scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
try {
FileUtils.copyFile(scrFile, new File("c:\\Learning\\"+ name));
} catch (IOException e) {
e.printStackTrace();
}
}
WebDriver拾级而上·之十三 调用Java Script
在用selenium 1.X的时候常常会用到geteval_r()方法来执行一段js脚本来对页面进行处理。
当然selenium webdriver也提供这样的一个方法:JavascriptExecutor.executeScript(string)
例如:
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
public class SimpleExample {
public static void main(String[] args) {
ChromeDriver driver = new ChromeDriver();
driver.executeAsyncScript("arguments[0](); alert('Hello')");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
driver.switchTo().alert().accept();
}
}
上面是一个最简单的例子,打开一个浏览器,然后弹层一个alert框。注意这里的driver要被强制转换成JavascriptExecutor。
下面演示在打开51.com首页如何得到帐号输入框中显示的字符,并打印输出。
package com.test;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class Test_JsExecutor {
public static void main(String[] args) {
String url = "http://www.51.com";
//打开chrome
WebDriver dr = new ChromeDriver();
String js = "var user_input = document.getElementByIdx_x_x_x(\"passport_51_user\").title;return user_input;";
String title = (String)((JavascriptExecutor)dr).executeScript( js);
System.out.println(title);
dr.quit();
}
}
输出结果为:用户名/彩虹号/邮箱
其他用例:
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("(function(){
inventoryGridMgr.setTableFieldValue('"+ inventoryId + "','"
+ fieldName + "','"+ value + "');
}
)()"
);
WebDriver拾级而上·之十四 RemoteWebDriver
当本机上没有浏览器,需要远程调用浏览器进行自动化测试时,需要用到RemoteWebDirver.
一、使用RemoteWebDriver
import java.io.File;
import java.net.URL;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
public class Testing {
public void myTest()throws Exception {
WebDriver driver = newRemoteWebDriver(
new URL("http://localhost:4446/wd/hub"),
DesiredCapabilities.firefox());
driver.get("http://www.google.com");
// RemoteWebDriverdoes not implement the TakesScreenshot class
// if the driver doeshave the Capabilities to take a screenshot
// then Augmenter willadd the TakesScreenshot methods to the instance
WebDriveraugmentedDriver = new Augmenter().augment(driver);
File screenshot =((TakesScreenshot)augmentedDriver).
getScreenshotAs(OutputType.FILE);
}
}
二、SeleniumServer
在使用RemoteDriver时,必须在远程服务器启动一个SeleniumServer:
java -jar selenium-server-standalone-2.20.0.jar -port 4446
三、How to setFirefox profile using RemoteWebDriver
profile = new FirefoxProfile();
profile.setPreference("general.useragent.override",testData.getUserAgent());
capabilities = DesiredCapabilities.firefox();
capabilities.setCapability("firefox_profile", profile);
driver = new RemoteWebDriver(new URL(“http://localhost:4446/wd/hub”),capabilities);
driverWait = new WebDriverWait(driver,TestConstant.WAIT_ELEMENT_TO_LOAD);
driver.get("http://www.google.com");
WebDriver拾级而上·之十五 拖曳动作模拟
如何把一个元素拖放到另一个元素里面
下面这个页面是一个演示拖放元素的页面,你可以把左右页面中的条目拖放到右边的div框中。
http://koyoz.com/demo/html/drag-drop/drag-drop.htm
Java代码
package com.test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.interactions.Actions;
public class Test_DragAndDrop {
public static void main(String[] args) {
WebDriver dr = new ChromeDriver();
dr.get("http://koyoz.com/demo/html/drag-drop/drag-drop.html");
//首先new出要拖入的页面元素对象element和目标对象target,然后进行拖入。
WebElement element = dr.findElement(By.id("item1"));
WebElement target = dr.findElement(By.id("drop"));
(new Actions(dr)).dragAndDrop(element, target).perform();
//利用循环把其它item也拖入
String id="item" ;
for(int i=2;i<=6;i++){
String item = id+i;
(new Actions(dr)).dragAndDrop(dr.findElement(By.id(item)), target).perform();
}
//dr.quit();
}
}
=================================================
附:如何利用Actions类模拟鼠标和键盘的操作
actions类,主要定义了一些模拟用户的鼠标mouse,键盘keyboard操作。对于这些操作,使用perform()方法进行执行。
actions类可以完成单一的操作,也可以完成几个操作的组合。
单一的操作
单一的操作是指鼠标和键盘的一个操作。如鼠标左键按下、弹起或输入一个字符串等。
前面涉及到鼠标键盘操作的一些方法,都可以使用actions类中的方法实现,比如:click,sendkeys。
WebElement element = dr.findElement(By.id("test"));
WebElement element1 = dr.findElement(By.id("test1"));
element.sendKeys("test");
element1.click;
用Actions类就可以这样实现:
//新建一个action
Actions action=new Actions(driver);
//操作
WebElement element=dr.findElement(By.id("test"));
WebElement element1=dr.findElement(By.id("su"));
action.sendKeys(element,"test").perform();
action.moveToElement(element1);
action.click().perform();
看起来用Actions类实现click和sendKeys有点烦索
组合操作
组合操作就是几个动作连在一起进行操作。如对一个元素的拖放。
(new Actions(dr)).dragAndDrop(dr.findElement(By.id(item)), target).perform();
可以直接调用dragAndDrip()方法,也可以像下面濱示的一样把几个操作放一起实现
Action dragAndDrop = builder.clickAndHold(someElement)
.moveToElement(otherElement)
.release(otherElement)
.build().perform();
其他鼠标或键盘操作方法可以具体看一下API里面的org.openqa.selenium.interactions.Actions类
WebDriver拾级而上·之十六 Table控件的处理
对于Table控件selenium webdriver里没有方法能处理,所以要自己封装一个方法
HTML代码(table.html放在桌面)
<html>
<head>
<title>Table</title>
</head>
<body>
<table border="1" id="myTable">
<tr>
<th>HeadingA(1 ,1)</th>
<th>HeadingB(1 ,2)</th>
<th>HeadingC(1 ,3)</th>
</tr>
<tr>
<td>2, 1</td>
<td>2, 2</td>
<td>2, 3</td>
</tr>
<tr>
<td>3, 1</td>
<td>3, 2</td>
<td>3, 3</td>
</tr>
</table>
</body>
</html>
JAVA代码
package com.test;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
public class Test_table {
private WebDriver driver;
Test_table(WebDriver driver){
this.driver = driver;
}
public String getCellText(By by,String tableCellAddress) {
//得到table元素对象
WebElement table = driver.findElement(by);
//对所要查找的单元格位置字符串进行分解,得到其对应行、列。
int index = tableCellAddress.trim().indexOf('.');
int row = Integer.parseInt(tableCellAddress.substring(0, index));
int cell = Integer.parseInt(tableCellAddress.substring(index+1));
//得到table表中所有行对象,并得到所要查询的行对象。
List<WebElement> rows = table.findElements(By.tagName("tr"));
WebElement theRow = rows.get(row);
//调用getCell方法得到对应的列对象,然后得到要查询的文本。
String text = getCell(theRow, cell).getText();
return text;
}
private WebElement getCell(WebElement Row,int cell){
List<WebElement> cells;
WebElement target = null;
//列里面有"<th>"、"<td>"两种标签,所以分开处理。
if(Row.findElements(By.tagName("th")).size()>0){
cells = Row.findElements(By.tagName("th"));
target = cells.get(cell);
}
if(Row.findElements(By.tagName("td")).size()>0){
cells = Row.findElements(By.tagName("td"));
target = cells.get(cell);
}
return target;
}
public static void main(String[] args) {
String url = "file:///C:/Documents and Settings/fy/桌面/table.html";
//打开chrome
WebDriver dr = new ChromeDriver();
dr.get(url);
Test_table table = new Test_table(dr);
System.out.println(table.getCellText(By.id("myTable"), "0.2"));
System.out.println(table.getCellText(By.id("myTable"), "2.1"));
dr.quit();
}
}
页面输出:
HeadingC(1 ,3)
3, 2
WebDriver拾级而上·之十七 断言
1.操作action:
模拟用户与 Web 应用程序的交互。一般用于操作应用程序的状态。
如点击链接,选择选项的方式进行工作。如果一个动作执行失败,或是有错误,当前的测试将会停止执行。
操作中常见命令有:
open(打开页面)
click(点击)
clickAndWait(点击并等待)
type(文本类型)
select(选择下拉菜单)
selectWindow(选择弹出窗口)
pause(等待指定时间,以毫秒为单位,即要睡眠的时间)
setSpeed(设定执行速度。以毫秒延迟间隔长度。默认没有延迟,即为0)
setTimeout(指定等待动作完成的等待时间。默认为30秒。
需要等待的动作包括了OPEN 和WAITFOR)
goBack(模拟用户点击其浏览器上的“back”按钮)
close(模拟用户点击弹出窗体或表单标题栏上的”关闭”按钮)
click与clickAndWait的区别:
例如对比录制脚本:
Comand Target
click css=input[type=submit] //句一
clickAndWait css=input[type=submiit] //句二
转成PHPUNIT后代码为:
$this->click(“css=input[type=submit]“); //此句对应上面的 句一
$this->click(“css=input[type=submit]“); //此句和下一句,对应上面的 句二
$this->waitForPageToLoad(“30000″);
区别在于:clickAndWait后会有一个默认的页面等待时间为30秒;而click没有等待时间;
Andwait这个后缀,告诉我们,该命令将使浏览器向服务器产生一个请求,使Selenium等待加载一个新的页面。
2.辅助accessors:
这是辅助工具。用于检查应用程序的状态并将结果存储到变量中。
如:storeElementPresent(locator,variableName)
其中参数:locator 表示元素定位器;variableName 用于存储结果的变量名。
即将locator定位到的状态存储到variableName变量中。
如果该元素出现返回true,否则返回false。可同断言一同使用。
3.断言assertion:
验证应用程序的状态是否同所期望的一致。
常见的断言包括:验证页面内容,如标题是否为X或当前位置是否正确,或是验证该复选框是否被勾选。
断言被用于三种模式: assert 、verify、waitfor
Assert 失败时,该测试将终止。
Verify 失败时,该测试将继续执行,并将错误记入日显示屏 。也就是说允许此单个 验证通过。确保应用程序在正确的页面上。
Waitfor用于等待某些条件变为真。可用于AJAX应用程序的测试。
如果该条件为真,他们将立即成功执行。如果该条件不为真,则将失败并暂停测试。直到超过当前所设定的超时时间。 一般跟setTimeout时间一起用
断言常用的有:
assertLocation(判断当前是在正确的页面)、
assertTitle(检查当前页面的title是否正确)、
assertValue(检查input的值, checkbox或radio,有值为”on”无为”off”)、
assertSelected(检查select的下拉菜单中选中是否正确)、
assertSelectedOptions(检查下拉菜单中的选项的是否正确)、
assertText(检查指定元素的文本)、
assertTextPresent(检查在当前给用户显示的页面上是否有出现指定的文本)、
assertTextNotPresent(检查在当前给用户显示的页面上是否没有出现指定的文本)、
assertAttribute(检查当前指定元素的属性的值)、
assertTable(检查table里的某个cell中的值)、
assertEditable(检查指定的input是否可以编辑)、
assertNotEditable(检查指定的input是否不可以编辑)、
assertAlert(检查是否有产生带指定message的alert对话框)、
waitForElementPresent (等待检验某元素的存在。为真时,则执行。)
WebDriver拾级而上·之十八 设置元素焦点
做自动化过程中,有时需要给某个元素设置焦点,在selenium1.0中提供了给元素设置焦点的方法。但是在2.0中并没有该办法。如果是输入框我们可以使用click方法,来设置焦点,但是对于link连接或者button如果通过click方法势必会跳转到另外页面或者提交了页面请求。通过尝试发现,如果在元素上进行右击,也可以设置焦点,但是会弹出一个菜单,这时我们可以通过按下键盘的esc键来取消右击弹出的菜单,这样焦点就可以设置成功了。下面我通过键盘和鼠标事件组合来实现该功能。代码如下:
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class TestActive {
WebDriver driver = null;
Actions action = null;
Robot robot = null;
@BeforeMethod
public void setUp(){
try {
robot = new Robot();
}
catch (AWTException e) {e.printStackTrace();}
System.setProperty(“webdriver.firefox.bin”, “D:/Firefox/firefox.exe”);
FirefoxProfile file = new FirefoxProfile();
DesiredCapabilities ds = DesiredCapabilities.firefox();
ds.setCapability(FirefoxDriver.PROFILE, file);
driver = new FirefoxDriver(ds);
action = new Actions(driver);
}
@AfterMethod
public void tearDown(){
}
@Test
public void start(){
driver.get(“http://www.baidu.com”);
driver.manage().window().maximize();
//查找你需要设置焦点的元素
WebElement button = driver.findElement(By.xpath(“//*[@id='nv']/a[5]“));
//对该元素进行右击操作
action.contextClick(button).perform();
//按ESC键返回,设置焦点成功
robot.keyPress(KeyEvent.VK_ESCAPE);
}
}
WebDriver拾级而上·之十九 常用方法
一、判断元素是否存在
public static boolean isElementPresent(WebDriver driver, By by) {
try {
driver.findElement(by);
return true;
} catch (NoSuchElementException e) {
return false;
}
}
二、判断Alert是否存在
public static boolean isAlertPresent(WebDriver driver) {
try {
driver.switchTo().alert();
return true;
} catch (NoAlertPresentException e) {
return false;
}
}
三、获取弹框的文本
acceptNextAlert弹框的操作类型,true点击确认操作,false点击取消操作
public static String closeAlertAndGetItsText(WebDriver driver,boolean acceptNextAlert) {
try {
Alert alert = driver.switchTo().alert();
String alertText = alert.getText();
if (acceptNextAlert) {
alert.accept();
} else {
alert.dismiss();
}
return alertText;
} finally {
acceptNextAlert = true;
}
}
TestNG深入
TestNG·一 基础概念
零、Testng环境部署
见http://blog.sina.com.cn/s/blog_6966650401012a53.html
一、 概论
TestNG,即Testing, Next Generation,下一代测试技术,是一套根据JUnit 和 NUnit思想而构建的利用注释来强化测试功能的一个测试框架,即可以用来做单元测试,也可以用来做集成测试。
因为TestNG是从Junit的思想构建而来,所以 TestNG具备junit等所不具备的多重功能。而且TestNG目前的使用比较广泛,google 的一个selenium自动化项目组即采用的是selenium rc的java 接口+ testNG结合的方式。
写一个测试通常分为三步:
1.编写测试业务逻辑,并且在你的代码中插入 TestNG annotations 。
2.在 testng.xml 或 build.xml 添加你的测试信息。例如类名,希望运行的组等等
3.运行TestNG.
文档中会使用到如下的概念:
1.一套测试(suite)由一个XML文件所表示。它能够包含一个或者多个测试, <suite> 标记来定义。
2.test由 <test> 标记来表示一个测试,并且可以包含一个或者多个TestNG类。
3.TestNG 类是包含至少一个TestNG annotation的 java类,由<class>标签描述并包含一个或多个测试方法。
4.测试方法,就是一个普通的Java方法,在由@Test标记。
testNG.xml
testNG的运行需要一个配置文件,默认为testng.xml,其描述了要运行哪些测试等配置。
编写testNG.xml如果没有书写提示,给在头部引入
<!DOCTYPE suite PUBLIC "--//beust.com//testng//testng 1.0//EN" "http://beust.com/testng/testng-1.0.dtd" >就会有提示了
注意:TestNG使用的是 正则表达式,而不是通配符。注意这二者的区别
例如:"anything" 是匹配于 ".*" -- 点和星号 -- 而不是星号 "*"
Groups
testNG可以将各个method存放在不同的group里面,然后运行的时候可以指定要运行的group。Group指定的方式如下:
@Test(groups = { "fast", "unit", "database" })
public void rowShouldBeInserted() {
}
二、实例
1.创建测试类Sum
package com.testNg;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import com.hpp.Sum;
public class NewTest {
private Sum newSum=new Sum();
@Test(groups = { "t1", "t2"})
public void f() {
int mysum=newSum.add(1, 2);
assertEquals(3,mysum,"Right");
}
@Test(groups = {"t2"})
public void f2() {
int mysum=newSum.add(2, 2);
assertEquals(3,mysum,"Right");//错误的用例
}
@Test(groups = { "t1"})
public void f3() {
int mysum=newSum.add(1, 2);
assertEquals(3,mysum,"Right");
}
}
3.修改testNG.xml
<?xml version="1.0" encoding="UTF-8"?>
<suite name="Suite" parallel="false">
<test name="Test">
<groups>
<run>
<include name="t1"/>
</run>
</groups>
<classes>
<class name="com.testNg.NewTest"/>
</classes>
</test> <!-- Test -->
</suite> <!-- Suite -->
当运行t1测试组时,就仅仅运行f()和f3()方法
运行结果
修改testNG.xml运行t2测试组,其中f2()方法故意留个错误,我们看下出现bug的情况
http://blog.csdn.net/zhu_ai_xin_520/article/details/6199217
http://www.cnblogs.com/zhangfei/category/419222.html
TestNG·二 测试组
一、测试组
TestNG 允许你将测试方法归类为不同的组。不仅仅是可以声明某个方法属于某个组,而且还可以让组包含其他的组。这样TestNG可以调用或者请求包含一组特定的组(或者正则表达式)而排除其他不需要组的集合。这样,如果你打算将测试分成两份的时候,就无需重新编译。这个特点,会给你在划分组的时候带来很大的灵活性。
例如,通常将测试划分为两种类别是再常见不过的了:
检查性测试(Check-in test):这些测试在你提交新代码之前就会运行。它们一般都是很快进行的,并且保证没有哪个基本的功能是不好使的。
功能性测试(Functional test):这些测试涵盖你的软件中所有的功能,并且至少每天运行一次,不过你也可能希望他们持续的运行。
典型的来说,检测性测试通常是功能性测试的一个子集。TestNG允许你根据个人感觉来进行组划分。例如,你可能希望把你所有的测试类都划归为"functest"组,并且额外的有几个方法属于"checkintest"组。
public class Test1 {
@Test(groups = { "functest", "checkintest" })
public void testMethod1() {
} @Test(groups = {"functest", "checkintest"} )
public void testMethod2() {
}
@Test(groups = { "functest" })
public void testMethod3() {
}
}
通过下面的脚本配置调用TestNG (include name="functest")
以上会运行上面那个类中所有的测试,当只需要使用checkintest进行调用的时候,把include中的name值换成checkintest,就仅仅运行testMethod1()和testMethod2()。
下面是另外一个例子。这次使用正则表达式。假定有些测试方法不应该运行在Linux环境下,脚本如下: @Test
public class Test1 {
@Test(groups = { "windows.checkintest" })
public void testWindowsOnly() {
}
@Test(groups = {"linux.checkintest"} )
public void testLinuxOnly() {
}
@Test(groups = { "windows.functest" )
public void testWindowsToo() {
}
}
然后你就可以使用下面这个 testng.xml 来只运行在Windows下的方法:
注意:TestNG使用的是正则表达式,而不是通配符。注意这二者的区别
例如,"anything" 是匹配于 ".*" -- 点和星号 -- 而不是星号 "*".
二、组中组
测试组也可以包含其他组。这样的组叫做“元组”(MetaGroups)。例如,你可能要定义一个组all来包含其他的组,chekcintest 和functest。"functest"本身只包含了组windows和linux,而"checkintest"仅仅包含windows。你就可以在属性文件中这样定义:
三、排除组
TestNG 允许你包含组,当然也可以排除组。譬如说,因为最近的改动,导致当前的测试中断,并且你还没有时间修复这些问题都是司空见惯的。但是,你还需要自己的功能测试可以正确运行,所以,只要简单的让这些不需要的测试失效就可以了。但是别忘记在以后需要的时候,要重新让其生效。
一个简单的办法来解决这个问题就是创建一个叫做"broken"组, 然后使得这些测试方法从属于那个组。例如上面的例子,假设我知道testMethod2()会中断,所以我希望使其失效:
@Test(groups = {"checkintest", "broken"} )
public void testMethod2() {
}
而我所需要做的一切就是从运行中排除这个组(exclude name="broken"):
通过这种办法,我们既可以得到整洁的测试运行,同时也能够跟踪那些需要稍后修正的中断的测试。
注意:你可以可以通过使用"enabled"属性来完成,这个属性适用于@Test 和 @Before/After annotation。
四、局部组
可以在类级别定义组,之后还可以在方法级定义组:
@Test(groups = { "checkin-test" })
public class All {
@Test(groups = { "func-test" )
public void method1() { ... }
public void method2() { ... }
}
在这个类中,method2() 类级组"checkin-test"的一部分,而method1()即属于"checkin-test"也属于"func-test"组。
---------------------------------------------------------------------------
TestNG·三 测试方法
一、设置参数
测试方法是可以带有参数的。每个测试方法都可以带有任意数量的参数,并且可以通过使用TestNG的@Parameters 向方法传递正确的参数。
设置方式有两种方法:使用 testng.xml 或者 Data Providers 。
(一)使用 testng.xml 设置参数
1.如果只使用相对简单的参数,可以在 testng.xml文件中指定:
@Parameters({ "first-name" })
@Test
public void testSingleString(String firstName) {
System.out.println("Invoked testString " + firstName);
assert "Cedric".equals(firstName);
}
在这段代码中,我们让 firstName 参数能够接到XML文件中叫做 first-name 参数的值。这个XML参数被定义在 testng.xml:
first-name" value="Cedric"/>
类似的,它也可以用在 @Before/After 和 @Factory 注解上:
@Parameters({ "datasource", "jdbcDriver" })
@BeforeMethod
public void beforeTest(String ds, String driver) {
m_dataSource = ...; // 查询数据源的值
m_jdbcDriver = driver;
}
这次有两个Java参数 ds 和 driver 会分别接收到来自属性datasource 和 jdbc-driver 所指定的值。
2.参数也可以通过 Optional 注释来声明:
@Parameters("db")
@Test
public void testNonExistentParameter(@Optional("mysql") String db) { ... }
如果在你的testng.xml文件中没有找到"db",你的测试方法就会使用 @Optional 中的值: "mysql"。
3.@Parameters 可以被放置到如下位置:
1.在任何已经被 @Test, @Before/After 或 @Factory 注解过的地方。
2.在测试类中至多被放到一个构造函数签。这样,TestNG才能在需要的时候使用 testng.xml 中特定的参数来实例化这个类。这个特性可以被用作初始化某些类中的值,以便稍后会被类中其他的方法所使用。
注意:
XML中的参数会按照Java参数在注解中出现的顺序被映射过去,并且如果数量不匹配,TestNG会报错。
参数是有作用范围的。在testng.xml 中,你即可以在 标签下声明,也可以在 下声明。如果两个参数都有相同的名字,那么,定义在 中的有优先权。这在你需要覆盖某些测试中特定参数的值时,会非常方便。
(二)使用DataProviders提供参数
在 testng.xml 中指定参数可能会有如下的不足:
1.如果你压根不用 testng.xml.
2.你需要传递复杂的参数,或者从Java中创建参数(复杂对象,对象从属性文件或者数据库中读取的etc...)
这样的话,你就可以使用Data Provider来给需要的测试提供参数。所谓数据提供者,就是一个能返回对象数组的数组的方法,并且这个方法被@DataProvider注解标注:
DataProvider的定义如下:
@DataProvider(name = "range-provider")
public Object[][] rangeData() {
int lower = 5;
int upper = 10;
return new Object[][] {
{ lower-1, lower, upper, false },
{ lower, lower, upper, true },
{ lower+1, lower, upper, true },
{ upper, lower, upper, true},
{ upper+1, lower, upper, false },
};
}
调用DataProvider的方式如下:
@Test(dataProvider = "range-provider")
public void testIsBetween(int n, int lower,int upper, boolean expected)
{
println("Received " + n + " " + lower + "-"+ upper + " expected: " + expected);
Assert.assertEquals(expected, isBetween(n, lower, upper));
}
被@Test标注的方法通过dataProvider属性指明其数据提供商。这个名字必须与@DataProvider(name="...") 中的名字相一致。
DataProvider返回的是一个Object的二维数组,二维数组中的每个一维数组都会传递给调用函数,作为参数使用。运行的时候,会发现, @Test标识的test method被执行的次数和object[][]包含的一维数组的个数是一致的,而@Test标识的函数的参数个数,也和object内一维数组内的元素数是一致的。
运行后的输出结果如下:
Received 4 5-10 expected: false
Received 5 5-10 expected: true
Received 6 5-10 expected: true
Received 10 5-10 expected: true
Received 11 5-10 expected: false
===============================================
Parameter Suite
Total tests run: 5, Failures: 0, Skips: 0
===============================================
(三)DataProviders扩展
默认的情况下,数据提供者会查找当前的测试类或者测试类的基类。如果你希望它能够被其他的类所使用,那么就要将其指定为static,并且通过 dataProviderClass 属性指定要使用的类:
public static class StaticProvider {
@DataProvider(name = "create")
public static Object[][] createData() {
return new Object[][] {
new Object[] { new Integer(42) }
}
}
}
public class MyTest {
@Test(dataProvider = "create", dataProviderClass = StaticProvider.class)
public void test(Integer n) {
// ...
}
}
Data Provider方法可以返回如下两种类型中的一种:
1.含有多个对象的数组 (Object[][]),其中第一个下标指明了测试方法要调用的次数,第二个下标则完全与测试方法中的参数类型和个数相匹配。上面的例子已经说明。
2.另外一个是迭代器 Iterator
下面是使用JDK5 的例子 (注意 JDK 1.4 的例子不适用泛型):
public Iterator createData() {
return new MyIterator(DATA);
)
@DataProvider(name = "test1")
public Iterator
如果你声明的 @DataProvider 使用 java.lang.reflect.Method 作为第一个参数,TestNG 会把当前的测试方法当成参数传给第一个参数。这一点在你的多个测试方法使用相同的@DataProvider的时候,并且你想要依据具体的测试方法返回不同的值时,特别有用。
例如,下面的代码它内部的 @DataProvider 中的测试方法的名字:
@DataProvider(name = "dp")
public Object[][] createData(Method m) {
System.out.println(m.getName());
return new Object[][] { new Object[] { "Cedric" }};
}
@Test(dataProvider = "dp")
public void test1(String s) {
}
@Test(dataProvider = "dp")
public void test2(String s) {
}
所以会显示:
test1
test2
Data provider可以通过属性 parallel实现并行运行:
@DataProvider(parallel = true)
// ...
使用XML文件运行的data provider享有相同的线程池,默认的大小是10.你可以通过修改该在 标签中的值来更改:
...
如果你需要让指定的几个data provider运行在不同的线程中,那么就必须通过不同的xml文件来运行。
二、依赖方法
有些时候,需要按照特定顺序调用测试方法。
1.确保在进行更多的方法测试之前,有一定数量的测试方法已经成功完成。
2.在初始化测试的时候,同时希望这个初始化方法也是一个测试方法( @Before/After 不会出现在最后生成的报告中)。
为此,你可以使用 @Test 中的 dependsOnMethods 或 dependsOnGroups 属性。
这两种依赖:
1.Hard dependencies(强依赖)。所有的被依赖方法必须成功运行。只要有一个出问题,测试就不会被调用,并且在报告中被标记为SKIP。
2.Soft dependencies(弱依赖)。 即便是有些依赖方法失败了,也一样运行。如果你只是需要保证你的测试方法按照顺序执行,而不关心他们的依赖方法是否成功。那么这种机制就非常有用。可以通过添加 "alwaysRun=true" 到 @Test 来实现软依赖。
硬依赖的例子:
@Test
public void serverStartedOk() {}
@Test(dependsOnMethods = { "serverStartedOk" })
public void method1() {}
此例中,method1() 依赖于方法 serverStartedOk(),从而保证 serverStartedOk() 总是先运行。
也可以让若干方法依赖于组:
@Test(groups = { "init" })
public void serverStartedOk() {}
@Test(groups = { "init" })
public void initEnvironment() {}
@Test(dependsOnGroups = { "init.* })
public void method1() {}
本例中,method1()依赖于匹配正则表达式"init.*"的组,由此保证了serverStartedOk()和 initEnvironment() 总是先于 method1()被调用。
注意:正如前面所说的那样,在相同组中的调用可是在测试中不保证顺序的。
如果你使用硬依赖,并且被依赖方法失败(alwaysRun=false,即默认是强依赖),依赖方法则不是被标记为FAIL而是SKIP。被跳过的方法会被在最后的报告中标记出来(HTML既不用红色也不是绿色所表示),主要是被跳过的方法不是必然失败,所以被标出来做以区别。
无论dependsOnGroups 还是 dependsOnMethods 都可以接受正则表达式作为参数。对于 dependsOnMethods,如果被依赖的方法有多个重载,那么所有的重载方法都会被调用。如果你只希望使用这些重载中的一个,那么就应该使用 dependsOnGroups。
三、类级注解
通常 @Test 也可以用来标注类,而不仅仅是方法:
@Test
public class Test1 {
public void test1() {
}
public void test2() {
}
}
处于类级的 @Test 会使得类中所有的public方法成为测试方法,而不管他们是否已经被标注。当然,你仍然可以用 @Test 注解重复标注测试方法,特别是要为其添加一些特别的属性时。
例如:
@Test
public class Test1 {
public void test1() {
}
@Test(groups = "g1")
public void test2() {
}
}
上例中 test1() 和 test2() 都被处理,不过在此之上 test2() 现在还属于组 "g1"。
http://testng.org/doc/index.html
http://blog.csdn.net/zhu_ai_xin_520/article/details/6199217
TestNG·四 测试方法之工厂
工厂允许你动态的创建测试。例如,假设你需要创建一个测试方法,并用它来多次访问一个web页面,而且每次都带有不同的参数:
public class TestWebServer {
@Test(parameters = { "number-of-times" })
public void accessPage(int numberOfTimes) {
while (numberOfTimes-- > 0) {
// access the web page
}
}
}
testng.xml:
<test name="T1">
<parameter name="number-of-times" value="10"/>
<class name= "TestWebServer" />
</test>
<test name="T2">
<parameter name="number-of-times" value="20"/>
<class name= "TestWebServer"/>
</test>
<test name="T3">
<parameter name="number-of-times" value="30"/>
<class name= "TestWebServer"/>
</test>
参数一旦多起来,就难以管理了,所以应该使用工厂来做:
public class WebTestFactory {
@Factory
public Object[] createInstances() {
Object[] result = new Object[10];
for (int i = 0; i < 10; i++) {
result[i] = new WebTest(i * 10);
return result;
}
}
新的测试类如下:
public class WebTest {
private int m_numberOfTimes;
public WebTest(int numberOfTimes) {
m_numberOfTimes = numberOfTimes;
}
@Test
public void testServer() {
for (int i = 0; i < m_numberOfTimes; i++) {
// access the web page
}
}
}
你的testng.xml 只需要引用包含工厂方法的类,而测试实例自己会在运行时创建:
<class name="WebTestFactory" />
工厂方法可以接受诸如 @Test 和 @Before/After 所标记的参数,并且会返回 Object[]。这些返回的对象可以是任何类(不一定是跟工厂方法相同的类),并且他们甚至都不需要TestNG注解(在例子中会被TestNG忽略掉)
TestNG·五 运行TestNG
一、并行运行于超时
可以通过在suite标签中使用 parallel 属性来让测试方法运行在不同的线程中。这个属性可以带有如下这样的值:
<suite name="My suite" parallel="methods" thread-count="5">
<suite name="My suite" parallel="tests" thread-count="5">
<suite name="My suite" parallel="classes" thread-count="5">
1.parallel="methods": TestNG 会在不同的线程中运行测试方法,除非那些互相依赖的方法。那些相互依赖的方法会运行在同一个线程中,并且遵照其执行顺序。
2.parallel="tests": TestNG 会在相同的线程中运行相同的<test>标记下的所有方法,但是每个<test>标签中的所有方法会运行在不同的线程中。这样就允许你把所有非线程安全的类分组到同一个<test>标签下,并且使其可以利用TestNG多线程的特性的同时,让这些类运行在相同的线程中。
3.parallel="classes": TestNG 会在相同线程中相同类中的运行所有的方法,但是每个类都会用不同的线程运行。
此外,属性 thread-count 允许你为当前的执行指定可以运行的线程数量。
注意:@Test 中的属性 timeOut 可以工作在并行和非并行两种模式下。
你也可以指定 @Test 方法在不同的线程中被调用。你可以使用属性 threadPoolSize 来实现:
@Test(threadPoolSize = 3, invocationCount = 10, timeOut = 10000)
public void testServer() {
上例中,方法 testServer 会在3个线程中调用10次。此外,10秒钟的超时设定也保证了这三个线程中的任何一个都永远不会阻塞当前被调用的线程。
二、再次运行失败的测试
每次测试suite出现失败的测试,TestNG 就会在输出目录中创建一个叫做 testng-failed.xml 的文件。这个XML文件包含了重新运行那些失败测试的必要信息,使得你可以无需运行整个测试就可以快速重新运行失败的测试。所以,一个典型的会话看起来像:
java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs testng.xml
java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -d test-outputs test-outputs/testng-failed.xml
要注意的是,testng-failed.xml 已经包含了所有失败方法运行时需要的依赖,所以完全可以保证上次失败的方法不会出现任何 SKIP。
三、JUnit测试
TestNG 能够运行 JUnit 测试。所有要做的工作就是在 testng.classNames 属性中设定要运行的JUnit测试类,并且把 testng.junit 属性设置为true:
<test name="Test1" junit="true">
<classes>
<!-- ... -->
TestNG 在这种情况下所表现的行为与 JUnit 相似:
1.所有类中要运行的测试方法由 test* 开头
2.如果类中有 setUp() 方法,则其会在每个测试方法执行前被调用
3.如果类中有 tearDown() 方法,则其会在每个测试方法之后被调用
4.如果测试类包含 suite() 方法,则所有的被这个方法返回的测试类都会被调用
四、方法拦截器
一旦TestNG 计算好了测试方法会以怎样的顺序调用,那么这些方法就会分为两组:
1.按照顺序运行的方法。这里所有的方法都有相关的依赖,并且所有这些方法按照特定顺序运行。
2.不定顺序运行的方法。这里的方法不属于第一个类别。方法的运行顺序是随机的,下一个说不准是什么(尽管如此,默认情况下TestNG会尝试通过类来组织方法)。
为了能够让你更好的控制第二种类别,TestNG定义如下接口:
public interface IMethodInterceptor {
List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context);
}
方法中叫做methods的那个列表参数包含了所有以不定序运行的方法。你的 intercept 方法也要返回一个 IMethodInstance列表,它可能是下面情况之一:
1.内容与参数中接收的一致,但是顺序不同
2.一组 IMethodInstance 对象
3.更大的一组 IMethodInstance 对象
一旦你定义了拦截器,就把它传递个TestNG,用下面的方式:
java -classpath "testng-jdk15.jar:test/build" org.testng.TestNG -listener test.methodinterceptors.NullMethodInterceptor /
-testclass test.methodinterceptors.FooTest
关于 ant 中对应的语法,参见 listeners 属性 ant 文档 中的说明。
http://testng.org/doc/ant.html
例如,下面是个方法拦截器会重新给方法排序,一遍“fast”组中的方法总是先执行:
public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
List<IMethodInstance> result = new ArrayList<IMethodInstance>();
for (IMethodInstance m : methods) {
Test test = m.getMethod().getMethod().getAnnotation(Test.class);
Set<String> groups = new HashSet<String>();
for (String group : test.groups()) {
groups.add(group);
}
if (groups.contains("fast")) {
result.add(0, m);
}
else {
result.add(m);
}
}
return result;
}
http://blog.csdn.net/zhu_ai_xin_520/article/details/6199217
TestNG·六 测试结果
一、成功、失败和断言
如果一个测试没有抛出任何异常就完成运行或者说抛出了期望的异常(参见@Test注解的expectedExceptions属性文档),就说,这个测试时成功的。
测试方法的组成常常包括抛出多个异常,或者包含各种各样的断言(使用Java "assert" 关键字)。一个 "assert" 失败会触发一个 AssertionErrorException,结果就是测试方法被标记为失败(记住,如果你看不到断言错误,要在加上 -ea 这个JVM参数)。
下面是个例子:
@Test
public void verifyLastName() {
assert "Beust".equals(m_lastName) : "Expected name Beust, for" + m_lastName;
}
TestNG 也包括 JUnit 的 Assert 类,允许你对复杂的对象执行断言:
import static org.testng.AssertJUnit.*;
//...
@Test
public void verify() {
assertEquals("Beust", m_lastName);
}
注意,上述代码使用了静态导入,以便能够使用 assertEquals 方法,而无需加上它的类前缀。
二、日志与结果
当运行SuiteRunner的时候会指定一个目录,之后测试的结果都会保存在一个在那个目录中叫做 index.html 的文件中。这个index文件指向其他多个HTML和文本文件,被指向的文件包含了整个测试的结果。你可以再这里看到一个例子。
通过使用监听器和报表器,可以很轻松的生成自己的TestNG报表:
(1)监听器 实现接口 org.testng.ITestListener ,并且会在测试开始、通过、失败等时刻实时通知
(2)报告器 实现接口 org.testng.IReporter ,并且当整个测试运行完毕之后才会通知。IReporter 接受一个对象列表,这些对象描述整个测试运行的情况
例如,如果你想要生成一个PDF报告,那么就不需要实时通知,所以用 IReporter。如果需要写一个实时报告,例如用在GUI上,还要在每次测试时(下面会有例子和解释)有进度条或者文本报告显示点 ("."),那么 ITestListener 是你最好的选择。
例如,如果你想要生成一个PDF报告,那么就不需要实时通知,所以用 IReporter。如果需要写一个实时报告,例如用在GUI上,还要在每次测试时(下面会有例子和解释)有进度条或者文本报告显示点 ("."),那么 ITestListener 是你最好的选择。
1.日志监听器
这里是对每个传递进来的测试显示"."的监听器,如果测试失败则显示 "F" ,跳过则是"S":
public class DotTestListener extends TestListenerAdapter {
private int m_count = 0;
@Override
public void onTestFailure(ITestResult tr) {
log("F");
}
@Override
public void onTestSkipped(ITestResult tr) {
log("S");
}
@Override
public void onTestSuccess(ITestResult tr) {
log(".");
}
private void log(String string) {
System.out.print(string);
if (m_count++ % 40 == 0) {
System.out.println("");
}
}
}
上例中,我们选择扩展 TestListenerAdapter ,它使用空方法实现了 ITestListener 。所以我不需要去重写那些我不需要的方法。如果喜欢可以直接实现接口。
这里是我使用这个新监听器调用TestNG的例子:
java -classpath testng.jar;%CLASSPATH% org.testng.TestNG -listener org.testng.reporters.DotTestListener test/testng.xml
2.日志报表
org.testng.IReporter 接口只有一个方法:
public void generateReport(List<ISuite> suites, String outputDirectory)
这个方法在 TestNG 中的所有测试都运行完毕之后被调用,这样你可以修改这个方法的参数,并且通过它们获得刚刚完成的测试的所有信息。
3.JUnit 报表
TestNG 包含了一个可以让TestNG的结果和输出的XML能够被JUnitReport所使用的监听器。这里有个例子,并且ant任务创建了这个报告:
<target name="reports">
<junitreport todir="test-report">
<fileset dir="test-output">
<include name="*/*.xml"/>
</fileset>
<report format="noframes" todir="test-report"/>
</junitreport>
</target>
注意: 由于JDK 1.5 和 JUnitReports 不兼容性,导致了frame版本不能够正常工作,所以你需要指定 "noframes" 使其能够正常工作。
4.报表 API
如果你要在HTML报告中显示日志信息,那么就要用到类 org.testng.Reporter:
Reporter.log("M3 WAS CALLED");
5. XML 报表
TestNG 提供一种XML报表器,使得能够捕捉到只适用于TestNG而不适用与JUnit报表的那些特定的信息。这在用户的测试环境必须要是用TestNG特定信息的XML,而JUnit又不能够提供这些信息的时候非常有用。下面就是这种报表器生成XML的一个例子:
<testng-results>
<suite name="Suite1">
<groups>
<group name="group1">
<method signature="com.test.TestOne.test2()" name="test2" class="com.test.TestOne"/>
<method signature="com.test.TestOne.test1()" name="test1" class="com.test.TestOne"/>
</group>
<group name="group2">
<method signature="com.test.TestOne.test2()" name="test2" class="com.test.TestOne"/>
</group>
</groups>
<test name="test1">
<class name="com.test.TestOne">
<test-method status="FAIL" signature="test1()" name="test1" duration-ms="0"
started-at="2007-05-28T12:14:37Z" description="someDescription2"
finished-at="2007-05-28T12:14:37Z">
<exception class="java.lang.AssertionError">
<short-stacktrace>java.lang.AssertionError
... Removed 22 stack frames
</short-stacktrace>
</exception>
</test-method>
<test-method status="PASS" signature="test2()" name="test2" duration-ms="0"
started-at="2007-05-28T12:14:37Z" description="someDescription1"
finished-at="2007-05-28T12:14:37Z">
</test-method>
<test-method status="PASS" signature="setUp()" name="setUp" is-config="true" duration-ms="15"
started-at="2007-05-28T12:14:37Z" finished-at="2007-05-28T12:14:37Z">
</test-method>
</class>
</test>
</suite>
</testng-results>
这个报表器是随着默认监听器一起诸如的,所以你默认的情况下就可以得到这种类型的输出。这个监听器提供了一些属性,可以修改报表来满足你的需要。下表包含了这些属性,并做简要说明:
属性 | 说明 | Default value |
---|---|---|
outputDirectory | 一个 String 指明了XML文件要存放的目录 | TestNG 输出目录 |
timestampFormat | 指定报表器生成日期字段的格式 | yyyy-MM-dd'T'HH:mm:ss'Z' |
fileFragmentationLevel | 值为1,2或3的整数,指定了XML文件的生成方式: 1 - 在一个文件里面生成所有的结果
2 - 每套测试都单独生成一个XML文件,这些文件被链接到一个主文件
3 - 同2,不过添加了引用那些套测试的测试用例的XML文件 | 1 |
splitClassAndPackageNames | boolean值,指明了对 <class> 元素的生成方式。例如,你在false的时候得到 <class class="com.test.MyTest"> ,在true的时候 <class class="MyTest" package="com.test"> 。 | false |
generateGroupsAttribute | boolean值指定了对<test-method> 元素是否生成 groups 属性。这个功能的目的是,对那些无需遍历整个<group> 元素,且只含有一个测试方法的组来说,可以提供一种更直接的解析组的方式。 | false |
stackTraceOutputMethod | 指定在发生异常的时候生成异常,追踪弹栈信息的类型,有如下可选值: 0 - 无弹栈信息 (只有异常类和消息).
1 - 尖端的弹栈信息,从上到下只有几行
2 - 带有所有内部异常的完整的弹栈方式
3 - 短长两种弹栈方式 | 2 |
generateDependsOnMethods | 对于<test-method>元素,使用这个属性来开启/关闭 depends-on-methods属性。 | true |
generateDependsOnGroups | 对于<test-method>属性开启depends-on-groups 属性。 | true |
为了配置报表器,你可以在命令行下使用 -reporter 选项,或者在 Ant 任务中嵌入<reporter> 元素。对于每个情况,你都必须指明类org.testng.reporters.XMLReporter。但是要注意你不能够配置内建的报表器,因为这个只使用默认配置。如果你的确需要XML报表,并且使用自定义配置,那么你就不得不手工完成。可以通过自己添加一两个方法,并且禁用默认监听器达到目的。
http://testng.org/doc/selenium.html
TestNG·七 annotation
TestNG中用到的 annotation的快速预览及其属性。
http://testng.org/doc/documentation-main.html
@BeforeSuite: 被注释的方法将在所有测试运行前运行
@AfterSuite: 被注释的方法将在所有测试运行后运行
@BeforeTest: 被注释的方法将在测试运行前运行
@AfterTest: 被注释的方法将在测试运行后运行
@BeforeGroups: 被配置的方法将在列表中的gourp前运行。这个方法保证在第一个属于这些组的测试方法调用前立即执行。
@AfterGroups: 被配置的方法将在列表中的 gourp后运行。这个方法保证在最后一个属于这些组的测试方法调用后立即执行。
@BeforeClass: 被注释的方法将在当前类的第一个测试方法调用前运行。
@AfterClass: 被注释的方法将在当前类的所有测试方法调用后运行。
@BeforeMethod: 被注释的方法将在每一个测试方法调用前运行。
@AfterMethod: 被注释的方法将在每一个测试方法调用后运行。
属性:
alwaysRun
1.对于每个before方法(beforeSuite, beforeTest,beforeTestClass和beforeTestMethod, 但是不包括 beforeGroups):如果设置为 true,被配置的方法将总是运行而不管它属于哪个组。
2.对于after方法(afterSuite, afterClass, ...): 如果设置为 true,被配置的方法甚至在一个或多个先调用的方法失败或被忽略时也将运行。
dependsOnGroups 这个方法依赖的组列表
dependsOnMethods 这个方法依赖的方法列表
enabled 这个类的方法是否激活
groups 这个类或方法所属的分组列表
inheritGroups 如果设置为 true,这个方法被属于在类级别被@Test annotation 指定的组
DataProvider 标记一个方法用于为测试方法提供数据。
被注释的方法必须返回 Object[][], 其中每个Object[]可以指派为这个测试方法的参数列表。
这个DataProvider接收数据@Test方法需要使用一个和当前注释相同名称的 dataProvider名称
name 这个 DataProvider的名称
@Factory 标记方法作为一个返回对象的工厂,这些对象将被TestNG用于作为测试类。这个方法必须返回 Object[]
@Parameters 描述如何传递参数给
@Test 方法
alwaysRun 如果设置为 true,这个测试方法将总是运行,甚至当它依赖的方法失败时。
dataProvider 这个测试方法的 data provider的名称
dataProviderClass 用于查找 data provider的类。如果不指定,将在当前测试方法所在的类或者它的基类上查找 data provider。如果这个属性被指定, 则 data provider方法需要是指定类的 static方法。
value 用于填充这个方法的参数的变量列表
dependsOnGroups 当前方法依赖的组列表
dependsOnMethods 当前方法依赖的方法列表
escription 当前方法的描述
enabled 当前类的方法/方法是否被激活
expectedExceptions 测试方法期望抛出的异常列表。如果没有异常或者抛出的不是列表中的任何一个,当前方法都将标记为失败.
invocationCount 当前方法被调用的次数
successPercentage 当前方法期望的成功率
sequential 如果设置为 true,当前测试类上的所有方法保证按照顺序运行。甚至测试们在parallel="true"的情况下.这个属性只能用于类级别,如果用于方法级别将被忽略。
timeOut 当前方法容许花费的最大时间,单位毫秒。
threadPoolSize 当前方法的线程池大小。方法将被多线程调用,次数由 invocationCount参数指定
注意:如果 invocationCount没有指定则这个属性将被忽略
注:
上面是 TestNG中用到的 annotation列表,从中我们可以看到 TestNG提供的一些特性
1. before 方法和after 方法 带来了足够丰富的测试生命周期控制
2. dependsOnGroups/dependsOnMethods 提供了依赖检查机制,并可以严格控制执行顺序
3. DataProvider 使得对同一个方法的测试覆盖变的非常轻松,非常适合进行边界测试,只要给出多种测试数据就可以针对一个测试方法进行覆盖
4. expectedExceptions 使得异常测试变的非常轻松
5. invocationCount/threadPoolSize 终于可以简单的直接进行多线程测试了,这个绝对是 junit的超级弱项,回想 junit中那个万恶的
System.exist(0)...
6. timeOut 终于不用死等然后手工强行关闭测试,TestNG 想的太周到了
groups 当前类/方法所属的组列表
TestNG·八 并发测试
一、 Concurrent testing:
下面的例子是输出进程ID,threadPoolSize用来指明线程池的大小,也就是并发的线程数目是多少
5次调用,有3个线程可调用
@Test(invocationCount = 5, threadPoolSize = 3,groups = { "t9"})
public void smallThreadPool() {
System.out.println("Thread#: " +Thread.currentThread().getId());
}
页面输出
[TestNG] Running:
E:\android\selenium\test_wdng_java\src\testing.xml
Thread#: 13
Thread#: 15
Thread#: 14
Thread#: 15
Thread#: 15
===============================================
Suite
Total tests run: 5, Failures: 0, Skips: 0
===============================================
若改成5次调用,有5个线程可调用
Thread#: 10
Thread#: 14
Thread#: 12
Thread#: 11
Thread#: 13
二、Concurrent running of tests
TestNG可以以多线程的模式运行所有的test,这样可以获得最大的运行速度,最大限度的节约执行时间。当然,并发运行也是有代价的,就是需要我们的代码是线程安全的。
并发运行测试的话,需要我们指定运行的配置文件,一个示例如下:
<suite name="Concurrent Suite" parallel="methods" thread-count="2" verbose="1" >
<>……<>
</suite>
1.Parallel=”methods”的意思是指TestNG会将method作为并发的元子单位,即每个method运行在自己的thread中。如果parallel=”tests”,则指会将 test 作为并发的元子单位
2.Thread-count=”2”是指,运行的时候,并发度为2,同时会有两个线程在运行。
例:
@Test(groups = { "t8"})
public void aThreadPool() {
System.out.println("#ThreadA: " +Thread.currentThread().getId());
}
@Test(groups = { "t8"})
public void bThreadPool() {
System.out.println("#ThreadB: " +Thread.currentThread().getId());
}
@Test(groups = { "t8"})
public void cThreadPool() {
System.out.println("#ThreadC: " +Thread.currentThread().getId());
}
--------------------------------------------------------------------
<test name="Test" parallel="methods" thread-count="2">
输出结果:
#ThreadB: 11
#ThreadC: 11
#ThreadA: 10
===============================================
Suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================
改成<test name="Test" parallel="tests" thread-count="2">
页面输出(因为aThreadPool(),bThreadPool(),cThreadPool()都在一个test下面)
#ThreadA: 1
#ThreadB: 1
#ThreadC: 1
===============================================
Suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================
TestNG·九 使用testng-xslt改写testng的测试报告
用TestNG测试后,自动会生成html的测试报告,不过自动生成的测试报告太难看了,所以我们可以用 testNG-xslt 来美化下。
1. 在官方网站上下载testNG-xslt ,http://testng-xslt.googlecode.com/files/testng-xslt-1.1.zip
2. 解压后把saxon-8.7.jar放到项目的lib目录(可能需要自己新建)下
3. 然后再把/src/main/resources/testng-results.xsl放到你的测试目录里,或是随便一个什么目录下
4. 创建一个简单的build.xml,放在项目目录下, 如下,绿框的地方自己参数化:
name :是项目的名字;
in和style:对应的是testng生成报告的xml和xsl;
out:是要用testNGxslt生成报告的文件名和路径;
expresssion:是要用testNGxslt生成报告的路径。
5.控制台切换到build.xml所在的目录下,执行ant transform就可以了,之后在output目录下就可以看到一个index1.html文件,打开它就是测试报告了。
当然也可用相对路径来配置build.xml
lib.dir 指的是:与build.xml在同一级目录下,名为lib的文件夹的路径
TestNG·十 自定义日志记录
一、创建自定义日志记录类继承自testng的接口TestListenerAdapter
package testng.listenerTest;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
public class CustomListener extends TestListenerAdapter{
private int m_count = 0;
@Override
public void onTestFailure(ITestResult tr) {
log(tr.getName()+ "--Test method failed\n");
}
@Override
public void onTestSkipped(ITestResult tr) {
log(tr.getName()+ "--Test method skipped\n");
}
@Override
public void onTestSuccess(ITestResult tr) {
log(tr.getName()+ "--Test method success\n");
}
private void log(String string) {
System.out.print(string);
if (++m_count % 40 == 0) {
System.out.println("");
}
}
}
二、创建一个测试用例类
package testng.sortTest;
import org.testng.Assert;
import org.testng.annotations.Test;
public class Order {
@Test
public void a() {
System.out.println("a");
Assert.assertEquals(1,1);
}
@Test(enabled = false)
public void b() {
System.out.println("b");
Assert.assertEquals(1,1);
}
@Test
public void c() {
System.out.println("c");
Assert.assertEquals(1,2);
}
}
三、创建 testng.xml
四、运行 testng.xml
控制台输出:
a
a--Test method success
c
c--Test method failed
===============================================
Suite
Total tests run: 2, Failures: 1, Skips: 0
===============================================
TestNG·十一 TestNG报告API
虽然TestNG自身提供了简洁的测试报告,可能我们想自己编写漂亮的测试,
那么怎么获取测试过程中的相关测试数据呢?
可以直接调用ITestResult中的api,
例如:
@AfterMethod
public void cleanTestCase(ITestResult testResult) {
System.out.println("testResult.getName():"+testResult.getName());
System.out.println("testResult.getTestName():"+testResult.getTestName());
System.out.println("testResult.getTestContext():"+testResult.getTestContext());
System.out.println("testResult.getInstanceName():"+testResult.getInstanceName());
System.out.println("testResult.getInstance():"+testResult.getInstance());
System.out.println("testResult.getThrowable():"+testResult.getThrowable());
}
控制台打印可见:
testResult.getName():testLogin
testResult.getTestName():null
testResult.getTestContext():org.testng.TestRunner@5a330c39
testResult.getInstanceName():com.lu.automation.mobile.testcases.TestLoginPage
testResult.getInstance():com.lu.automation.mobile.testcases.TestLoginPage@38b3b029
testResult.getThrowable():org.openqa.selenium.TimeoutException: Timed out after 60 seconds waiting for element to no longer be visible: By.id: title_bar_right_tv
ITestResult.getName() 是测试用例名
ITestResult.getStatus() 测试执行结果: ITestResult.SUCCESS,ITestResult.FAILURE,ITestResult.SKIP
ITestResult.getThrowable() 测试的报错信息
WebDriver·TestNg 学以致用
WebDriver & TestNG·参数化及用例排序
WebDriver & TestNG·架构
追求代码质量: 使用 Selenium 和 TestNG 进行编程式测试
用 STAF+Selenium 实现并行的自动化测试框架
JUnit
JUnit4单元测试
一、生成TestCase
1.新建待测试的方法
package test.method;
public class Calculator {
public int add(int x,int y){
System.out.println("add");
return x+y;
}
public int minus(int x,int y){
System.out.println("minus");
return x-y;
}
public int multiplication(int x,int y){
System.out.println("multiplication");
return x*y;
}
public int divide(int x,int y){
System.out.println("divide");
return x/y;
}
}
注:要将JUnit4单元测试包引入这个项目:在该项目上点右键,点【属性】,
在弹出的属性窗口中,首先在左边选择【Java Build Path】,然后到右上选择【Libraries】标签,
之后在最右边点击【Add Library…】按钮,然后在新弹出的对话框中选择JUnit4并点击确定,
JUnit4软件包就被包含进我们这个项目了。
2.生成测试用例
a.在项目中,选中 Calculator.java 右键【new】-【Junit Test Case】
b.在弹出的对话框中进行设置
点击【Next >】,系统会自动列出你这个类中包含的方法,选择你要进行测试的方法。此例中,我们仅对“加、减、乘、除”四个方法进行测试。点击【Finish】。
3.系统会自动生成一个新类CalculatorTest,修改测试用例,代码如下
package test.method;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
public class CalculatorTest {
private static Calculator c = new Calculator();
//“在任何一个测试执行之前必须执行的代码”就是一个Fixture,我们用@Before来标注它
@Before
public void setUp() throws Exception {
System.out.println("@Before-setUp-Begin");
}
//如果“在任何测试执行之后需要进行的收尾工作”也是一个Fixture,使用@After
@After
public void setUpEnd() throws Exception {
System.out.println("@Before-setUp-End");
System.out.println("------------------------");
}
@Test
public void testAdd() {
assertEquals(2, c.add(1, 1));
}
@Test
public void testMinus() {
assertEquals(2, c.minus(3, 1));
}
//@Ignore标注,这个标注的含义就是“某些方法尚未完成,暂不参与此次测试”。
@Test
@Ignore("Multiplication() Not yet implemented")
public void testMultiplication() {
assertEquals(4, c.multiplication(2, 2));
}
@Test
public void testDivision() {
assertEquals(2, c.divide(4, 1));
}
//使用@Test标注的expected属性,将要检验的异常传递给他,就能自动检测是否抛出了指定的异常
@Test(expected = ArithmeticException.class)
public void testDivisionbyZero() {
assertEquals(0, c.divide(4, 0));
}
}
4.运行测试代码,右键测试用例类 CalculatorTest.java,【run as】-【Junit Case】,运行结果如下
第1,2,5个用例通过,第3个用例忽略不运行,第4个用例出错,即有bug
5.代码讲解
A.创建一个待测试的对象。
为了测试Calculator类,我们必须创建一个calculator对象。
private static Calculator calculator = new Calculator();
B.测试方法的声明
在测试类中,并不是每一个方法都是用于测试的,必须使用“标注”来明确表明哪些是测试方法。
“标注”也是JDK5的一个新特性,以一个“@”作为开头。这些标注都是JUnit4自定义的。
C.忽略测试某些尚未完成的方法。
如果你已经把该方法的测试用例写完,但该方法尚未完成,那么测试的时候一定是“失败”。这种失败和真正的失败是有区别的,因此JUnit提供了一种方法在这种测试函数的前面加上@Ignore标注,这个标注的含义就是“某些方法尚未完成,暂不参与此次测试”。这样的话测试结果就会提示你有几个测试被忽略,而不是失败。一旦你完成了相应函数,只需要把@Ignore标注删去,就可以进行正常的测试。
D.Fixture(暂且翻译为“固定代码段”)
Fixture的含义就是“在某些阶段必然被调用的代码”。
我们非常希望每一个测试都是独立的,相互之间没有任何耦合度,就很有必要在执行每一个测试之前,对Calculator对象进行一个“复原”操作,以消除其他测试造成的影响。因此,“在任何一个测试执行之前必须执行的代码”就是一个Fixture,我们用@Before来标注它。同理,如果“在任何测试执行之后需要进行的收尾工作”也是一个Fixture,使用@After来标注。
如果我们希望的是在所有测试一开始读一次文件,所有测试结束之后释放文件,而不是每次测试都读文件。@BeforeClass 和 @AfterClass两个Fixture来帮我们实现这个功能。
只在测试用例初始化时执行@BeforeClass方法,当所有测试执行完毕之后,执行@AfterClass进行收尾工作。在这里要注意一下,每个测试类只能有一个方法被标注为@BeforeClass 或 @AfterClass,并且该方法必须是Public和Static的。
二、JUnit高级
1.限时测试
当单元测试遇到方法死循环时,要采取一些预防措施。限时测试是一个很好的解决方案。我们给这些测试函数设定一个执行时间,超过了这个时间,他们就会被系统强行终止,并且系统还会向你汇报该函数结束的原因是因为超时,这样你就可以发现这些Bug了。要实现这一功能,只需要给@Test标注加一个参数即可,代码如下:
@Test(timeout = 1000)
public void 测试方法名() {
assertEquals(2, calculator.getResult());
}
Timeout参数表明了你要设定的时间,单位为毫秒,因此1000就代表1秒。
2.测试异常
如果你觉得一个函数应该抛出异常,但是它没抛出,这当然是Bug。例如,我们写的计算器类有除法功能,如果除数是一个0,那么必然要抛出“除0异常”。因此,我们很有必要对这些进行测试。代码如下:
@Test(expected = ArithmeticException.class)
public void testDivisionbyZero() {
assertEquals(0, c.divide(4, 0));
}
如上述代码所示,我们需要使用@Test标注的expected属性,将我们要检验的异常传递给他,这样JUnit框架就能自动帮我们检测是否抛出了我们指定的异常。
如果抛出异常则测试用例通过,如果没有抛出异常则不通过。
3. Runner (运行器)
在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。JUnit中有一个默认Runner,如果你没有指定,那么系统自动使用默认Runner来运行你的代码。换句话说,下面两段代码含义是完全一样的:
import org.junit.internal.runners.TestClassRunner;
import org.junit.runner.RunWith;
//使用了系统默认的TestClassRunner,与下面代码完全一样
public class CalculatorTest ...{...}
@RunWith(TestClassRunner.class)
public class CalculatorTest ...{...}
从上述例子可以看出,指定一个Runner,需要使用@RunWith标注,并且把你所指定的Runner作为参数传递给它。另外一个要注意的是,@RunWith是用来修饰类的,而不是用来修饰函数的。只要对一个类指定了Runner,那么这个类中的所有函数都被这个Runner来调用。最后,不要忘了包含相应的Package哦,上面的例子对这一点写的很清楚了。
4.参数化测试
a.首先,要为这种测试专门生成一个新的类,而不能与其他测试共用同一个类
b.然后,要为这个类指定一个Runner,不能使用默认的Runner了,因为特殊的功能要用特殊的Runner : ParameterizedRunner。
c.定义一个待测试的类,并且定义适当的变量,一部分用于存放参数,一部分用于存放期待的结果。
d.定义测试数据的集合,也就是上述的data()方法,该方法可以任意命名,但是必须使用@Parameters标注进行修饰。
e.写测试用例
f.代码如下
package test.method;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.junit.Test;
@RunWith(Parameterized.class)
public class TestCaseParameterized_CalculatorAdd {
private static Calculator c = new Calculator();
private int param1,param2;
private int result;
@Parameters
public static Collection data() {//定义测试数据的集合
return Arrays.asList(new Object[][] {
{2,1,1},
{0,0,0},
{-3,-2,-1},
});
}
//构造函数,对变量进行初始化
public TestCaseParameterized_CalculatorAdd(int result,int param1, int param2){
this.param1 = param1;
this.param2 = param2;
this.result = result;
}
@Test
public void testAddParameterized() {
System.out.println(param1+"+"+param2+"="+result);
assertEquals(result, c.add(param1, param2));
}
}
g.运行结果如下
5.打包测试
通过前面的介绍我们可以感觉到,在一个项目中,只写一个测试类是不可能的,我们会写出很多很多个测试类。可是这些测试类必须一个一个的执行,也是比较麻烦的事情。鉴于此,JUnit为我们提供了打包测试的功能,将所有需要运行的测试类集中起来,一次性的运行完毕,大大的方便了我们的测试工作。具体代码如下:
package test.method;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
CalculatorTest.class,
TestCaseParameterized_CalculatorAdd.class,
TestCaseParameterized_CalculatorMinus.class
})
public class AllCalculatorTestCases {
}
可以看到,这个功能也需要使用一个特殊的Runner,因此我们需要向@RunWith标注传递一个参数Suite.class。同时,我们还需要另外一个标注@Suite.SuiteClasses,来表明这个类是一个打包测试类。我们把需要打包的类作为参数传递给该标注就可以了。有了这两个标注之后,就已经完整的表达了所有的含义,因此下面的类已经无关紧要,随便起一个类名,内容全部为空既可。
运行结果如下
三、JUnit元数据
@Before: 使用了该元数据的方法在每个测试方法执行之前都要执行一次。
@After: 使用了该元数据的方法在每个测试方法执行之后都要执行一次。
注意:@Before和@After标示的方法只能各有一个。这个相当于取代了JUnit以前版本中的setUp和tearDown方法,当然你还可以继续叫这个名字,不过JUnit不会霸道的要求你这么做了。
@BeforeClass: 使用了该元数据的方法在所有测试方法执行之前执行且仅执行一次。
@AfterClass: 使用了该元数据的方法在所有测试方法执行之后执行且仅执行一次。
@Test(expected=*.class) 在JUnit4.0之前,对错误的测试,我们只能通过fail来产生一个错误,并在try块里面assertTrue(true)来测试。现在,通过@Test元数据中的expected属性。expected属性的值是一个异常的类型
@Test(timeout=xxx):
该元数据传入了一个时间(毫秒)给测试方法,
如果测试方法在制定的时间之内没有运行完,则测试也失败。
@ignore:
该元数据标记的测试方法在测试中会被忽略。当测试的方法还没有实现,或者测试的方法已经过时,或者在某种条件下才能测试该方法(比如需要一个数据库联接,而在本地测试的时候,数据库并没有连接),那么使用该标签来标示这个方法。同时,你可以为该标签传递一个String的参数,来表明为什么会忽略这个测试方法。比如:@ignore(“该方法还没有实现”),在执行的时候,仅会报告该方法没有实现,而不会运行测试方法。
WebDriver(Ruby)拾级而上
RubyWebDriver·A环境部署
一、下载及ruby安装包
下载地址:http://rubyinstaller.org/downloads/
或者 http://vdisk.weibo.com/s/5Vx44
安装完成后验证,
1.检查Ruby是否安装成功及其版本号,在cmd中输入 ruby -v
2.检查rubygems(类似与appstore)是否安装成功及其版本号,输入gem -v
3.更新rubygems到最新版本,输入gem update --system
二、安装selenium-webdriver
1.同样在控制台中输入gem install selenium-webdriver
(卸载gem uninstall selenium-webdriver)
2.验证是否安装成功,在控制台输入gem list selenium-webdriver
三、本步骤可无视,但是
如果上面2步因为ffi无法安装或者其他原因导致无法安装成功
可以去下载绿色版使用http://vdisk.weibo.com/s/72r30
解压缩以后将bin目录,比如说D:\Program Files\Ruby192\bin
加到环境变量里面去path的最后 ;D:\Program Files\Ruby192\bin
四、irb调试selenium webdriver
控制台输入irb
然后输入1+1 回车
ok 配置成功
五、查看ruby文档
控制台输入: gem server
浏览器打开: http://localhost:8808
http://localhost:8808/doc_root/selenium-webdriver-2.21.2/rdoc/index.html
六、 watir 部署
下载地址:http://rubyforge.org/frs/?group_id=104
控制台输入: gem install watir-1.5.4.gem
gem list
API文档
http://rubydoc.info/gems/watir-webdriver/frames
============================================
rubymine-4-5
xe
== LICENSE BEGIN =====
70500-12042010
00000mReSjjHMId!KbSZVdsTDRUuAW
u52MV2MGQs8aFSJS4vfIhFm66Ej0Y8
Q6K9Rrb!3TMtdTgAG7sOtg9Q8DWsz9
===== LICENSE END =====
RubyWebDriver·B浏览器的简单操作
一、打开一个测试浏览器
具体代码如下。需要注意的是如果使用chrome进行测试,那么必须下载部署chromedriver.exe。
下载后放在ruby安装目录下,比如 D:\Ruby193\bin 下即可。
下载地址: http://chromedriver.storage.googleapis.com/index.html
require 'rubygems'
require 'selenium-webdriver'
# 打开firefox
dr = Selenium::WebDriver.for :firefox
dr = Selenium::WebDriver.for :ff
# 打开ie
dr = Selenium::WebDriver.for :ie
dr = Selenium::WebDriver.for :internet_explorer
# 打开chrome
dr = Selenium::WebDriver.for :chrome
二、打开1个具体的url
打开浏览器后我们需要转到我们的测试url。下面的代码可以达成这个目的。
require 'rubygems'
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
# 使用get方法
dr.get url
# 使用navigate方法,然后再调用to方法
dr.navigate.to url
三、关闭浏览器
测试结束后往往需要关闭浏览器,下面的代码可以完成这个任务。
require 'rubygems'
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
dr.get url
# 使用quit方法 关闭所有页面
dr.quit
# 使用close方法 关闭本次执行打开的页面
dr.close
四、返回当前页面的url
需要返回当前测试页面的url,比如在使用soso进行搜索时,当我们提交了搜索请求后,soso返回的url应该是包含我们所需要搜索的关键字的。
例如我们搜索webdriver,那么提交搜索请求后,页面应当转到url为http://www.soso.com /q?pid=s.idx&cid=s.idx&w=webdriver的页面,这时候我们取到这个页面的url,然后通过正则表达式去 匹配一下就能够得到我们所搜索的关键字了。具体代码如下。
require 'rubygems'
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
url = 'http://www.soso.com'
dr.navigate.to url
search_input = dr.find_element :id => 's_input'
search_input.send_keys 'webdriver'
search_input.submit
match = dr.current_url
puts match
dr.close
输出:http://www.soso.com/q?pid=s.idx&cid=s.idx.se&w=webdriver
注:search_input.submit 等同于 submit_button.find_element(:id,'s_button').click
五、返回当前页面的title
require 'rubygems'
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
url = 'http://www.soso.com'
dr.navigate.to url
puts dr.title
六、其他方法
window_handles : 返回当前所有打开浏览器的窗口句柄
window_handle : 返回当前的浏览器的窗口句柄
page_source : 返回当前页面的源码
visible? : 当前浏览器是否可见,并不保证支持所有浏览器
webdriver窗口最大化方法
dr = Selenium::WebDriver.for :ff
dr.manage.window.maximize
RubyWebDriver·C执行一段js脚本
有时候在进行自动化测试时需要在页面上执行一段js脚本,这个时候就需要用到execute_script方法了。
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :ff
url = 'http://www.soso.com'
dr.navigate.to url
sleep 3
js = <<JS
q = document.getElementByIdx_x_x("tb");
q.style.border = "1px solid red";
JS
dr.execute_script js
上面的代码打开了SoSo的首页,并高亮显示了id为”tb”的div。
-----------------------------------------------------------------
下面的例子演示了在打开QQ首页的时候如何自动focus到页面上的soso搜索框
require 'rubygems'
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :ff
url = 'http://www.qq.com'
dr.navigate.to url
sleep 3
js = <<JS
p = document.getElementByIdx_x_x("smart_input")
p.focus()
JS
dr.execute_script js
当dr = Selenium::WebDriver.for :ff换成dr = Selenium::WebDriver.for :ie时,js执行不了,
这个问题待研究,除ie浏览器,别的浏览器都是ok的
RubyWebDriver·D元素定位
selenium-Webdriver的对象定位方法非常的丰富和强大。一般来说强大的对象定位都会提供如下的一些方法
单个对象的定位方法
多个对象的定位方法
层级定位
一、定位单个对象
在定位单个对象时,selenium-Webdriver支持使用如下的一些属性对元素进行定位。
Selenium::WebDriver.for :firefox.find_element(:class => 'classname')
等价
dr = Selenium::WebDriver.for :firefox
dr.find_element(:class => 'classname')
:class => 'class name',
:class_name => 'class name',
:css => 'css selector',
:id => 'id',
:link => 'link text',
:link_text => 'link text',
:name => 'name',
:partial_link_text => 'partial link text',
:tag_name => 'tag name',
: xpath => 'xpath',
1.使用class或class_name进行定位
当所定位的对象具有class属性的时候我们可以通过class或class_name来定位该对象。
下面的例子定位了google首页上class为”gsfi”的input。
#encoding: gbk
require 'selenium-webdriver'
url = %q{http://www.google.com/}
dr = Selenium::WebDriver.for :firefox
dr.navigate.to url
sleep 3
new_icon = dr.find_element(:class => 'gsfi')
#输出new_icon 控件类型
puts "控件的类型:#{new_icon.tag_name}"
dr.close
2.使用id属性定位
google首页的搜索输入框的html代码如下:
<input class="gsfi" disabled="" style="border: medium none; padding: 0pt; margin: 0pt; height: auto; width: 100%; position: absolute; z-index: 1; background-color: transparent; color: silver; left: 0px; visibility: hidden;" id="gs_htif0" dir="ltr">
在进行定位前我们先动态定义highlight方法,该方法的作用是高亮显示有id属性的页面元素。
随后的代码演示了如何使用id属性来定位google首页上的搜索输入框。
当使用id定位到正确的元素后,highlight方法会将该元素以红色高亮显示,借此也可以验证代码是否工作正常。
require 'selenium-webdriver'
Selenium::WebDriver::Element.module_eval do
def highlight
e_id = self.attribute('id')
puts "e_id=#{e_id}"
js = <<JS
document.getElementByIdx_x_x_x_x("#{e_id}").style.border="3px solid red"
JS
@bridge.executeScript(js) if e_id
end
end
url = %q{http://www.google.com}
dr = Selenium::WebDriver.for :firefox
dr.navigate.to url
sleep 3
s_input = dr.find_element(:id=>'gs_tti0')
puts "Element Type:#{s_input.tag_name}"
s_input.highlight
sleep 10
dr.close
输出:
Element Type:td
e_id=gs_tti0
3.使用name属性定位
soso首页的搜索输入框的html代码如下:
<input type="text" name="w" smartpid="sb.idx" smartch="sb.c.idx" id="s_input" value="">
# 同样定位soso首页的搜索框,使得搜索输入框的边框加框变红色
#by name
require 'selenium-webdriver'
Selenium::WebDriver::Element.module_eval do
def highlight
e_id = self.attribute('id')
puts "ID:#{e_id}"
js = <<JS
document.getElementByIdx_x_x_x_x("#{e_id}").style.border = "10px solid red"
JS
@bridge.executeScript(js) if e_id
end
end
url = %q{http://www.soso.com/}
dr = Selenium::WebDriver.for :firefox
dr.navigate.to url
sleep 1
s_input = dr.find_element(:name => 'w')
puts "Element Type:#{s_input.tag_name}"
s_input.highlight
sleep 15
dr.close
输出:
Element Type:input
ID:s_input
4.使用css属性定位 支持css3语法
soso首页的搜索输入框的html代码如下:
<input type="text" name="w" smartpid="sb.idx" smartch="sb.c.idx" id="s_input" value="">
# css属性定位soso首页搜索框
s_input = dr.find_element(:css => '#s_input')
5.使用xpath定位
soso首页的搜索输入框的html代码如下:
<input type="text" name="w" smartpid="sb.idx" smartch="sb.c.idx" id="s_input" value="">
# 使用xpath定位soso首页搜索框
s_input = dr.find_element(:xpath => %Q{//input[@id='s_input' and @name='w']})
6.使用其他方式定位
在定位link对象的时候,可以使用link和link_text属性;
另外还可以使用tag_name属性定位任意元素;
二、定位多个元素
find_elements方法可以定位一组对象,例如
# 定位页面上所有的link对象
require 'selenium-webdriver'
url = %q{http://www.baidu.com/}
dr = Selenium::WebDriver.for :firefox
dr.navigate.to url
sleep 3
all_links = dr.find_elements(:tag_name, 'a')
#输出a标签的类型,值和链接地址
all_links.each do |b| puts "#{b.class},#{b.text},#{b.attribute('href')}"
end
dr.close
上面的代码返回页面上所有link对象的数组
输出:(有几个a标签就输出几行)
Selenium::WebDriver::Element
三、层级定位
层级定位的思想是先定位父对象,然后再从父对象中精确定位出其我们需要选取的后代元素。
层级定位一般的应用场景是无法直接定位到需要选取的元素,但是其父元素比较容易定位,通过定位父元素再遍历其子元素选择需要的目标元素,或者需要定位某个元素下所有的子元素。
下面的代码演示了如何使用层级定位选取id为bm的div下所有的link元素,并打印出所有其href
links = dr.find_element(:id, 'bm').find_elements(:css => 'a')
links.each do |l|
puts l['href']
end
http://localhost:8808/doc_root/selenium-webdriver-2.21.2/rdoc/Selenium/WebDriver/Element.html#method-i-text
RubyWebDriver·E定位frame中元素
一、准备工作,做2个测试页面
frame.html
part1.htm
二、switch_to方法
switch_to方法会new 1个TargetLocator对象,使用该对象的frame方法可以将当前识别的”主体”移动到需要定位的frame上去。
switch_to.frame(iframe 的 ID)
switch_to.window(window 的 handle)
switch_to.alert
#encoding: gbk
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
#相对路径(与rb文件在同一个目录下)
#frame_file = 'file:///'+File.expand_path(File.join(File.dirname(__FILE__),'frame.html'))
#绝对路径
frame_file = 'D:\rb\webdriver\frame.html'
dr.navigate.to frame_file
puts "Page Title:#{dr.title}"
#定位default content 上的p元素
tagP=dr.find_element(:id=>'frame_p')
puts "(1)Tag P text:#{tagP.text}"
#将当前识别主体移动到id为f_1的frame上去
dr.switch_to.frame('f_1')
tagP=dr.find_element(:id=>'f_p1_id')
puts "(2)Tag P text:#{tagP.text}"
=begin
#此时再去定位frame外的p 元素将出现错误
tagP=dr.find_element(:id=>'frame_p')
puts "(4)Tag P text:#{tagP.text}"
=end
#点击frame上的button
button = dr.find_element(:id=>'part1_btn')
button.click # -->a alert will popup
alert = dr.switch_to.alert
alert.accept
#将识别的主体切换出frame
dr.switch_to.default_content
tagP=dr.find_element(:id=>'frame_p')
puts "(3)Tag P text:#{tagP.text}"
dr.close
输出:
Page Title:Frame
(1)Tag P text:Outside frame
(2)Tag P text:This is Part1.f_p1_id
(3)Tag P text:Outside frame
三、特殊情况
对于IE浏览器可能会阻止js或frame,我们需要设置一下
【工具】→【Internet 选项】→【高级】→ 【安全(项)】,勾选【允许活动内容在我的计算机上的文件中运行】
RubyWebDriver·F捕获弹出窗口
一、准备测试页面
window.html
<html>
<head><title>Popup Window</title></head>
<body>
<a id = "soso" href = "http://www.soso.com/" target = "_blank">click me</a>
</body>
</html>
二、通过handle来定位弹出窗口
#encoding: gbk
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
#window_file = 'file:///'+File.expand_path(File.join(File.dirname(__FILE__),'window.html'))
window_file = 'D:\rb\webdriver\window.html'
dr.navigate.to window_file
dr.find_element(:id=>'soso').click
#在弹出窗后使用,获取所有弹出的浏览器窗口的句柄
all_handles = dr.window_handles
puts "All_handles:"
puts all_handles
#获取当前浏览器窗口的句柄
now_handle = dr.window_handle
puts "-"*50
puts "now_handle:#{now_handle}"
#差值就是新弹出窗口的句柄
all_handles.each do |h|
unless h == now_handle
#进入当前弹出窗
dr.switch_to.window(h)
new_win_id=dr.find_element(:id =>'s_input')
puts "tag_name:#{new_win_id.tag_name}"
end
end
puts "-"*50
dr.switch_to.window(now_handle)
puts "text:#{dr.find_element(:id =>'soso').text}"
sleep 3
dr.close
sleep 3
dr.quit
输出:
All_handles:
{69054e2a-5228-436c-bd1e-d0c2f5ea8c82}
{b4a5e37f-5bac-4c81-a3f1-864d00f0751b}
--------------------------------------------------
now_handle:{69054e2a-5228-436c-bd1e-d0c2f5ea8c82}
tag_name:input
--------------------------------------------------
text:click me
捕获或者说定位弹出窗口的关键在于获得弹出窗口的handle。
1.在上面的代码里,使用了windowhandles方法获取所有弹出的浏览器窗口的句柄,然后使用windowhandle方法来获取当前浏览器窗口的句柄,将这两个值的差值就是新弹出窗口的句柄。
2.在获取新弹出窗口的句柄后,使用switchto.window(newwindow_handle)方法,将新窗口的句柄作为参数传入既可捕获到新窗口了。
如果想回到以前的窗口定位元素,那么再调用1次switch_to.window方法,传入之前窗口的句柄既可达到目的。
RubyWebDriver·G处理弹出窗alert和confirm
以前使用watir 1.6x 的时候处理页面javascript弹出的alert和confrim窗口时必须借助autoit工具来辅助执行。
在selenium webdriver中,confirm和alert的处理再也不需要借助任何第三方工具了。
一、准备测试页面
下面的html页面上有1个名为click的button,点击该button后就会弹出1个alert窗口。
<html>
<head>
<title>Alert</title>
</head>
<body>
<input id = "btn" value = "click" type = "button" onclick = "alert('hello');"/>
</body>
</html>
二、selenium webdriver处理alert的代码如下:
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
#alert_page = 'D:\rb\webdriver\alert.html'
alert_page = 'file://'.concat File.expand_path(File.join(File.dirname(__FILE__),'alert.html'))
dr.navigate.to alert_page
dr.find_element(:id=>'btn').click
alert = dr.switch_to.alert
sleep 3
puts alert.text
alert.accept
dr.switch_to.default_content
puts "Title:#{dr.title}"
dr.quit
输出:
hello
Title:Alert
上面代码的思路是先点击id为btn的按钮,然后a = dr.switch_to.alert返回了1个alert element(暂时如此理解好了)并赋值给变量a。这样a就代表了alert,使用puts a.text语句可以输出alert的内容,这里会打印出’hello’。
三、accept方法表示点击确认,当弹出窗口为confrim时,accept方法也表示确认,dismiss方法表示取消。
RubyWebDriver·H操作select下拉框
在selenium-webdriver中定位select list的方法比较简单,用id和name等属性可以很方便的将select给找出来,但是怎么去选择下拉框中的某一项呢?
思路是这样的,首先定位到select list元素,然后找出该select list下所有的option,点击该option element既可,以下面的html代码为例
<html>
<head>
<title>Select</title>
</head>
<body>
<span>select demo</span>
<select id =
"s"
name =
"ns"
>
<option value =
"0"
>Op1</option>
<option value =
"1"
>Op2</option>
<option value =
"2"
>Op3</option>
<option value =
"3"
>Op4</option>
</select>
</body>
</html>
通过下面的代码可以选择到下拉框中的第2个option,也就是text为Op2的选项
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
#alert_page = 'D:\rb\webdriver\alert.html'
select_page = 'file://'.concat File.expand_path(File.join(File.dirname(__FILE__),'select.html'))
dr.navigate.to select_page
sleep 5
puts "Title:#{dr.title}"
#获取所有下拉框选项
list_option=dr.find_element(:id => 's').find_elements(:tag_name => 'option')
puts "There has #{list_option.length} options"
list_option.each{|op| puts op.text+"!"}
ele_op1=dr.find_element(:id => 's').find_elements(:tag_name => 'option')[1]
ele_op1.click
puts "Now option is:#{ele_op1.text }"
#可以合并成
#dr.find_element(:id => 's').find_elements(:tag_name => 'option')[1].click
dr.quit
输出:
Title:SelectPage
There has 4 options
Op1!
Op2!
Op3!
Op4!
Now option is:Op2
RubyWebDriver·I设置等待时间
web的自动化测试中,我们经常会遇到这样一种情况:点击1个按钮,页面上会弹出1个iframe,这时候脚本就需要去等待iframe加载完毕才能进行后续的操作。
在这种情况下,我们一般的处理思路是等待被等待对象上的某个子元素出现,当这个子元素出现时我们就认为该对象已经加载完毕,代码可以继续往下执行了。
selenium-webdriver为我们提供了一个Wait类来完成类似的等待功能。
下面的html代码实现了这样的一种效果:点击click按钮5秒钟后,页面上会出现一个红色的div块。我们需要写一段自动化脚本去捕获这个出现的div,然后高亮之。
timeout.html
<html>
<head>
<title>Set Timeout</title>
<style>
.red_box {background-color: red; width = 20%; height: 100px; border: none;}
</style>
<script>
function show_div(){
setTimeout("create_div()", 3000);
}
function create_div(){
d = document_createElement_x_x('div');
d.className = "red_box";
document.body.a(d);
}
</script>
</head>
<body>
<button id = "b" onclick = "show_div()">click</button>
</body>
</html>
下面的代码实现了高亮动态生成的div块的功能:
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
#select_file = 'file:///'.concat File.expand_path(File.join(File.dirname(__FILE__), 'timeout.html'))
select_file = 'D:\rb\webdriver\html\timeout.html'
dr.navigate.to select_file
dr.find_element(:id => 'b').click
wait = Selenium::WebDriver::Wait.new({:timeout => 30})
#box = wait.until {dr.find_element(:css => '.red_box')}
box = wait.until {dr.find_element(:class_name => 'red_box')}
dr.execute_script('arguments[0].style.border = "10px solid yellow"', box) #div will be highlight
dr.quit
Wait类的构造方法Wait.new接收1个hash参数。上面代码中使用:timeout这个key值表示最长等待时间。
Wait类的until方法接收1个block代码块,如果代码块返回值不为true的话,该方法将一直等待直到达到最长等待时间为止。如果一旦代 码块中的值为true了,则返回该代码块的返回值。 box = wait.until {dr.find_element(:css => '.red_box')}的作用就是等待class为red_box的div出现并返回该div对象。
进一步的思想下,如果某些页面在加载完成后会执行一些js函数,这些函数会延迟对dom树进行一些操作或者进行一些异步请求的处理,那么 webdriver目前是无法智能的等待这些函数执行完毕的,所以有时候就会出现页面在没有加载完毕的情况下(实际上dom已经加载完毕,只是异步请求或 延迟函数正在执行),webdriver继续进行后续代码的执行情况。这时候我们就需要灵活的使用Wait类进行等待了。
RubyWebDriver·J截图
require 'rubygems'
require 'selenium-webdriver'
dr = Selenium::WebDriver.for :firefox
#url = 'http://www.baidu.com'
url = 'http://blog.sina.com.cn/s/articlelist_1768318212_0_1.html'
dr.get url
#截图并保存路径,绝对/相对都可以
#dr.save_screenshot '.\baidu.png'
dr.save_screenshot 'D:\rb\webdriver\html\jietu.jpg'
dr.close
RubyWebDriver·K操作flash控件
我们以Open Testing论坛发帖为例(http://bbs.opentest.cn/forum-64-1.html),当需要插入附件时,如下图所示,页面的上传控件是用flash写的,这时WebDriver已经无法满足我们的需求了,需要引用第三方工具AutoIt来帮助我们处理flash控件。
AutoIt部署参见:http://blog.sina.com.cn/s/blog_6966650401017jr2.html
代码如下:
#encoding:utf-8
require 'selenium-webdriver'
require 'test/unit'
require 'rubygems'
require 'win32ole'
class OpentestCase<Test::Unit::TestCase
#def setup
@dr = Selenium::WebDriver.for :chrome
#@dr = Selenium::WebDriver.for :firefox
#@url ="http://bbs.opentest.cn"
@url ="http://bbs.opentest.cn/forum-64-1.html"
@dr.get @url
sleep 1
#end
#def test_should_enter_into_post_page_successfully
#@dr.find_element(:xpath,"//*[@id='category_63']/table/tbody/tr[1]/td[2]/h2/a").click
sleep 1
@dr.find_element(:id,'newspecial').click
sleep 1
@dr.find_element(:id,'subject').send_keys("best post a message")
#添加附件
@dr.find_element(:id,'e_attach').click
@dr.find_element(:id,'SWFUpload_1').click
#ruby实例化1个autoit对象
at = WIN32OLE.new('AutoItX3.Control')
#暂停脚本的执行直至指定窗口存在(出现)为止 WinWait ( "窗口标题" [, "窗口文本" [, 超时时间(秒)]] )
at.WinWait("选择要上载的文件,通过:bbs.opentest.cn", "", 2)
#激活指定的窗口(设置焦点到该窗口)。 WinActivate ( "窗口标题" [, "窗口文本"] )
at.WinActivate("选择要上载的文件,通过:bbs.opentest.cn")
sleep 2
#设置输入焦点到指定窗口的某个控件上 ControlFocus ( "窗口标题", "窗口文本", 控件ID)
#at.ControlFocus( "选择要上载的文件,通过:bbs.opentest.cn", "", "[CLASS:Edit; INSTANCE:1]" )
at.ControlFocus( "选择要上载的文件,通过:bbs.opentest.cn", "", "1148" )
at.Send("D:\\1.jpg")
sleep 2
#模拟谷歌浏览器的键盘操作:alt+o
at.Send("!O")
#火狐 alt+s
#at.Send("!S")
sleep 2
@dr.find_element(:id,'attach_confirms').click
@dr.switch_to.frame('e_iframe')
sleep 5
@dr.find_element(:xpath,"//body").send_keys("best post a message body")
sleep 2
@dr.switch_to.default_content
@dr.find_element(:id,'postsubmit').click
#end
=begin
def test_postpage_title_correctly
title = @dr.find_element(:id,'thread_subject').text
assert(title,"best post a message")
end
=end
#def teardown
@dr.close
#end
end
WebDriver(Ruby)学以致用
RubyWebDriver学以致用·A基础demo
整个学以致用的案例,是一个登录腾讯的soso首页的流程。
1.以未登录状态,进入soso首页,http://www.soso.com
2.点击【登录】按钮,此时会发现,由于我的QQ是登录状态,soso默认可以讲QQ号作为soso的账号进行快速登录
3.我们选择【其他账号登录】
4.输入用户名 密码,点击【登录】按钮,进行登录操作
以下是脚本代码:
#encoding = gbk
require 'selenium-webdriver'
#chrome = Selenium::WebDriver.for :chrome
dr = Selenium::WebDriver.for :firefox
url = 'http://www.soso.com'
dr.get url
#定位登录按钮
dr.find_element(:id,'ua').find_elements(:css,'a')[0].click
#定位登录浮层
dr.switch_to.frame('login_frame')
sleep 2
# 3种方式定位【使用其他号码登录】
#第一种,根据连接名进行定位,这种方式不提倡,因为可能会存在有字符编码的问题
#dr.find_element(:link_text,'使用其他号码登录').click
#第二种,根据css的a标签定位,为啥是[1]?因为【使用其他号码登录】控件,是switch选择器下第二个a标签中的元素。这里鄙视下哥的同学,害哥查了一段时间的代码,才查出原因
#dr.find_element(:id,'switch').find_elements(:css,'a')[1].click
#第三种,根据css的id.switch_login定位
dr.find_element(:id,'switch').find_element(:id,'switch_login').click
sleep 2
#输入能登录成功的账号
dr.find_element(:id,'u').send_keys("bestfei@ruby.com")
#能登录成功的账号的密码
dr.find_element(:id,'p').send_keys("niubility")
dr.find_element(:id,'login_button').click
#dr.find_element(:id,'s_logout').click
sleep 2
dr.quit
输出:
"D:\Program Files\Ruby192\bin\ruby.exe" -e $stdout.sync=true;$stderr.sync=true;load($0=ARGV.shift) D:/rb/webdriver/selenium_webdriver/soso_login_01.rb
Process finished with exit code 0
证明脚本正确
RubyWebDriver学以致用·B框架下的测试用例
A篇的demo可以正常执行通过,但是这并不代表就可以按照这个思路写测试代码了。自动化测试与手工测试是一样的,要有测试用例,有执行有结果,所以我们需要进一步的修改脚本。
As we all know,java环境中用JUnit进行单元测试、.net环境中使用NUit进行单元测试,Ruby selenium webdriver也需要一个类似这样的测试框架来执行测试用例,这个框架可以用test::Unit,也可以用文中所用的rspec
目标:利用测试框架rspec创建一个测试用例,运行上一节的代码
1.安装rspec
命令行中执行命令:gem install rspec
2.D盘下新建目录D:\rb\webdriver\spec
3.spec文件夹中新建文件soso_login_spec.rb
4.编写脚本
#encoding = gbk
require 'selenium-webdriver'
def gbk2utf8(string)
Iconv.conv('utf-8','gbk',string)
end
def utf82gbk(string)
Iconv.conv('gbk','utf-8',string)
end
describe "soso mainpage login" do
it "should return username and password is wrong" do
dr=Selenium::WebDriver.for :firefox
url='http://www.soso.com'
dr.get url
dr.find_element(:id,'ua').find_elements(:css,'a')[0].click
dr.switch_to.frame('login_frame')
sleep 1
dr.find_element(:id,'switch').find_element(:id,'switch_login').click
sleep 1
dr.find_element(:id,'u').send_keys("bestfei@ruby.com")
dr.find_element(:id,'p').send_keys("wrong")
#dr.find_element(:id=>'verifycode').send_keys("aaaaaa")
dr.find_element(:id,'login_button').click
#dos窗口运行这句
#puts utf82gbk(dr.find_element(:id=>'error_tips').text)
#rubymine 运行这句
puts dr.find_element(:id=>'error_tips').text
puts utf82gbk(dr.find_element(:id=>'error_tips').text).should eql("您输入的帐号或密码不正确,请重新输入。")
#puts utf82gbk(dr.find_element(:id=>'error_tips').text).should eql("您还没有输入验证码!")
dr.quit
end
end
当使用rubymine运行时候,页面结果
当使用dos命令启动时
d:
cd D:\rb\webdriver
rspec -f doc
RubyWebDriver学以致用·C面向对象编程
现在我们来改造下代码,让我们的代码看上去更有层次感,更可维护,更灵活。
我们把代码分成控件层,操作层,脚本实施层。
a.控件层:用于获取各种要操作的控件元素。
b.操作层:对获取的控件元素进行操作(点击、赋值等)
c.脚本执行层:执行测试用例
改造方法:
1.新建test_case_01base文件夹
2.test_case_01base文件夹下新建三个文件夹,分别为tool、action、spec。其中tool存放控件层代码,action存放操作层代码,spec存放测试用例
3.action文件夹下新建文件login_action.rb;
tool文件夹下新建文件login_tool.rb;
spec文件夹下新建文件login_spec.rb
4.login_tool.rb文件中编写如下代码
module LoginTool
def ua_link
@dr.find_element(:id,'ua').find_elements(:css,'a')
end
def login_link
ua_link[0]
end
def to_dialog_frame
begin
@dr.switch_to.frame('login_frame')
rescue
raise 'Can not switch to login dialog, make sure the dialog was open'
exit
end
end
def switch_link
@dr.find_element(:id,'switch').find_elements(:css,'a')
end
def login_switch_link
switch_link[1]
end
def usr_field
@dr.find_element(:id => 'u')
end
def psd_field
@dr.find_element(:id => 'p')
end
def login_btn
@dr.find_element(:id => 'login_button')
end
def err_message
utf82gbk(@dr.find_element(:id=>'error_tips').text)
end
def close_browser
@dr.close
end
end
def gbk2utf8(string)
Iconv.conv('utf-8','gbk',string)
end
def utf82gbk(string)
Iconv.conv('gbk','utf-8',string)
end
5.login_action.rb文件中编写如下代码
require File.dirname(__FILE__)+'/../tool/login_tool'
class LoginAction
include LoginTool
def initialize(dr)
@dr ||=dr
end
def open_login_dialog
login_link.click
#login_link.send_keys(:enter)
sleep 2
end
def open_switch_dialog
login_switch_link.click
#login_switch_link.send_keys(:enter)
sleep 2
end
def login(username,password)
open_login_dialog
to_dialog_frame
open_switch_dialog
usr_field.send_keys(username)
psd_field.send_keys(password)
login_btn.click
end
end
6.login_spec.rb文件中编写如下代码
#encoding: utf-8
require "rspec"
require 'yaml'
require 'selenium-webdriver'
require File.dirname(__FILE__)+'/../action/login_action'
require File.dirname(__FILE__)+'/../tool/login_tool'
describe "soso login" do
include LoginTool
before(:all) do
@dr=Selenium::WebDriver.for :firefox
@url='http://www.soso.com'
@dr.get @url
end
before(:each) do
@login_element=LoginAction.new(@dr)
end
it "should return username or password is wrong" do
begin
@login_element.login("bestfei@ruby.com","best")
#err_message.should eql ("您输入的帐号或密码不正确,请重新输入。意见反馈")
err_message.should eql (utf82gbk("您输入的帐号或密码不正确,请重新输入。"))
#err_message.should eql (utf82gbk("您还没有输入验证码!"))
ensure
close_browser
end
end
end
7.命令行中输入命令:
d:
cd D:\rb\test_case_01base
rspec -f doc
看到如下结果:
soso login
should return username and password is wrong
Finished in 19.2 seconds
1 example, 0 failures
RubyWebDriver学以致用·D测试用例集
C篇已经把脚本的各个功能进行了细分,现在我们来进一步来改进我们的脚本,本次改进的目的在于将逻辑与数据进行剥离,在原有3层(控件层,操作层,执行层)的基础上新增一层数据层,用于存放测试用例集。
a.控件层(tool):用于获取各种要操作的控件元素。
b.操作层(action):对获取的控件元素进行操作(点击、赋值等)。
c.脚本执行层(spec):执行测试用例。
d.数据层(config):存放测试用例集合。
改造代码如下,
1.控件层和操作层login_tool.rb和操作层login_action.rb 与C篇保持不变
2.修改脚本执行层login_spec.rb代码,
我们使用yaml,读取配置文件中测试数据,所以需要引用下require 'yaml'
#encoding: utf-8
require "rspec"
require 'yaml'
require 'selenium-webdriver'
require File.dirname(__FILE__)+'/../action/login_action'
require File.dirname(__FILE__)+'/../tool/login_tool'
describe "soso login" do
include LoginTool
before(:each) do
@problem=YAML.load(File.open(File.dirname(__FILE__)+'/../config/login_data.yml'))
@dr=Selenium::WebDriver.for :firefox
#@url='http://www.soso.com'
@url=@problem["data"]["mainPage"]["url"]
@dr.get @url
@login_element=LoginAction.new(@dr)
end
after(:each) do
close_browser
end
it "should return username and password is wrong" do
#@login_element.login("bestfei@ruby.com","best")
@login_element.login(@problem["data"]["login"]["wrong"]["username"],@problem["data"]["login"]["wrong"]["password"])
#err_message.should eql (utf82gbk("您输入的帐号或密码不正确,请重新输入。"))
err_message.should eql(utf82gbk(@problem["data"]["login"]["wrong"]["message"]))
end
it "should return username and password are not exist" do
@login_element.login(@problem["data"]["login"]["allNotExist"]["username"],@problem["data"]["login"]["allNotExist"]["password"])
err_message.should eql (utf82gbk(@problem["data"]["login"]["allNotExist"]["message"]))
end
it "should return username is not exist" do
@login_element.login(@problem["data"]["login"]["usernameNotExist"]["username"],@problem["data"]["login"]["usernameNotExist"]["password"])
err_message.should eql (utf82gbk(@problem["data"]["login"]["usernameNotExist"]["message"]))
end
it "should return password is not exist" do
@login_element.login(@problem["data"]["login"]["passwordNotExist"]["username"],@problem["data"]["login"]["passwordNotExist"]["password"])
err_message.should eql (utf82gbk(@problem["data"]["login"]["passwordNotExist"]["message"]))
end
end
3.新建config文件夹,新增文件login_data.yml
编码如下:
data:
mainPage:
url: http://www.soso.com
title: 搜搜更懂你
login:
wrong:
username: test
password: test
message: 请输入正确的QQ帐号!
allNotExist:
username:
password:
message: 您还没有输入帐号!
usernameNotExist:
username:
password: test
message: 您还没有输入帐号!
passwordNotExist:
username: test
password:
message: 您还没有输入密码!
最终工程结构如图:
最后我们运行脚本
d:
cd D:\rb\test_case_03morecase
rspec -f doc
修改上面一句可生成报告
rspec -f html > result.html
文件夹下生成result.html这个文件,双击此文件打开,里面便生成了测试报告
WebDriver(.Net C#)拾级而上
.NetWebDriver·A环境部署
一、新建Console Application项目,名为 webdriverdemo
二、下载 C# Selenium Client
http://docs.seleniumhq.org/download/
三、添加 selenium dotnet 引用
右键项目名,Add Reference - Browse - 选择selenium dotnet dll
四、下载最新的chromedriver.exe,放在目录C:\WINDOWS\system32 下即可。
下载地址: http://chromedriver.storage.googleapis.com/index.html
五、写第一个脚本,验证环境搭建完整。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
namespace webdriverdemo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Webdriver");
IWebDriver dr = new ChromeDriver();
dr.Navigate().GoToUrl("http://www.baidu.com/");
Console.WriteLine("Page Url:" + dr.Url);
Console.WriteLine("Page Title:" + dr.Title);
Console.WriteLine("Attribute value:" + dr.FindElement(By.Id("su1")).GetAttribute("value"));
Console.WriteLine("Attribute href:" +
dr.FindElement(By.Name("tj_tieba")).GetAttribute("href"));
dr.Quit();
Console.ReadKey();
}
}
}
输出如下:
Page Url:http://www.baidu.com/
Page Title:百度一下,你就知道
Attribute value:百度一下
Attribute href:http://tieba.baidu.com/
自此,我们的环境搭建完毕。
这里稍微讲解下脚本:
IWebDriver.Url 返回页面的地址
IWebDriver.Title 返回页面的Title
IWebDriver.FindElement(By.Id("su1")) 根据ID查找控件
IWebDriver.FindElement(By.Id("su1")).GetAttribute("value")); 返回控件的value属性