Selenium Web 自动化 - 测试框架(一)

2016-08-05

目录

1 框架结构雏形
2 把Java项目转变成Maven项目
3 加入TestNG配置文件
4 Eclipse编码修改
5 编写代码
  5.1 封装Selenium操作
  5.2 使用log4j进行日志输出
  5.3 封装测试平台和测试浏览器选择工具类
  5.4 根据key读取属性文件里面的value值
  5.5arrow插件解析
    5.5.1 ConfigReader.java
    5.5.2 负责监听测试运行状态和结果
    5.5.3 负责失败的用例重跑的监听器
    5.5.4 负责生成测试报告的监听器
  5.6 用例的开头和结尾设计
  5.7 页面类设计
  5.8 页面帮助类设计
  5.9 书写第一个用例
  5.10 完整的pom.xml和testng.xml
6 配置测试报告目录
7 填加driver
8 执行用例

源代码

自动化项目由maven+TestNG+selenium设计而成。

  • maven:是一个项目管理工具,主要用于项目构建,依赖管理,项目信息管理。这里主要用到它的jar包管理
  • TestNG:是一套根据JUnit 和NUnit思想而构建的利用注释来强化测试功能的一个测试框架,即可以用来做单元测试,也可以用来做集成测试。

1 框架结构雏形


返回

新建的一个java project,项目名为autotest,创建如下结构

图1 框架结构雏形

  • base:里面有个基类 (BaseParpare.java),这个类的主要作用是启动(启动浏览器使用了TetsNG的@BeforeClass)和关闭浏览器的作用(关闭浏览器使用了TetsNG的@AftereClass)
  • pages:页面元素类,每一个java类,都是一个页面,存放的都是对应页面的所有元素定位值。
  • pageshelper:这个包主要存放的是对应页面的帮助类
  • plugins:主要存放的是arrow插件以及第三方插件。
  • testcases:顾名思义就是存放测试用例的地方
  • utils:这个包主要是封装了各种工具类,Selenium api封装类,读取属性文件类和选择浏览器类等
  • config目录:存储框架类所需的所有属性文件,比如arrow的配置文件以及自定义的一些属性文件。
  • res目录:主要存放的是driver
  • result目录:存储测试框架运行测试用例生成的报告(包含log,截图等)

2 把Java项目转变成Maven项目


返回

右击项目->configure->covert to maven project,修改属性groupId=com.demo,

图2 Creat new POM

生成pom.xml如下所示:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>autotest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

其中:

  • Group Id:填写你组织机构,比如我这里就写的com.demo,例如要是百度可以写成com.baidu
  • Artifact Id:可以理解成为项目打包成jar包的ID 或者说 jar包名字,一般以项目名命名。

3 加入TestNG配置文件


返回

右击项目->TestNG->Convert to TestNG,生成testng.xml如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite">
<test name="Test">
</test> <!-- Test -->
</suite> <!-- Suite -->

4 Eclipse编码修改


返回

为了统一编码和以后为了避免乱码的出现,我们整个环境都使用UTF-8编码模式

Window-> Preferences,在弹出窗口Preferences中general->workspace, text file encoding 选择other 选项为UTF-8

5 编写代码


返回

名称 描述 链接
 这个类的主要作用是启动和关闭浏览器  5.6

这个包存放的都是对应页面的所有元素

 5.7
 这个包主要存放的是对应页面的帮助类  5.8
 主要存放的是arrow插件以及第三方插件  5.5
存放测试用例的地方。在这个包下,还会有很多子包,子包的个数根据你测试的系统的模块来划分  5.9
这个类配置了log  5.2
   5.4
 封装测试平台和测试浏览器选择工具类  5.3
 这个类封装了Selenium API  5.1
 Java Application默认带的library  
 项目依赖的jar  
 项目编译生成class文件存放目录,Eclipse会自动控制。  
arrow的配置文件  5.5.1
driver的配置文件  5.4 
 用ie浏览器测试所需要的driver  
 存储测试框架运行测试用例生成的报告(包含log,截图等)  
 可以不用管,由maven控制即可  
 maven的配置文件,项目核心配置,用于构建项目、自动下载项目依赖以及后续的和testng、jenkins配合持续集成等  5.10
   5.10

5.1 封装Selenium操作

SeleniumUtil.java:封装一下selenium的api (会调用SelectBrowser.java,后面补上这个类),代码如下:

package com.demo.test.utils;

import java.io.File;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.NoAlertPresentException;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.ITestResult; /**
* @Description 包装所有selenium的操作以及通用方法,简化用例中代码量
* */
public class SeleniumUtil {
/** 使用Log4j,第一步就是获取日志记录器,这个记录器将负责控制日志信息 */
public static Logger logger = Logger.getLogger(SeleniumUtil.class.getName());
public ITestResult it = null;
public WebDriver driver = null;
public WebDriver window = null; /***
* 启动浏览器并打开页面
* */
public void launchBrowser(String browserName, ITestContext context,String webUrl,int timeOut) {
SelectBrowser select = new SelectBrowser();
driver = select.selectExplorerByName(browserName, context);
try {
maxWindow(browserName);
waitForPageLoading(timeOut);
get(webUrl);
} catch (TimeoutException e) {
logger.warn("注意:页面没有完全加载出来,刷新重试!!");
refresh();
JavascriptExecutor js = (JavascriptExecutor)driver;
String status= (String)js.executeScript("return document.readyState"); logger.info("打印状态:"+status);
} } // ------------------------------- 对窗口进行操作 -----------------------------------
/**
* 最大化浏览器操作
* */
public void maxWindow(String browserName) {
logger.info("最大化浏览器:" + browserName);
driver.manage().window().maximize();
} /**
* 设定浏览器窗口大小: 设置浏览器窗口的大小有下面两个比较常见的用途:<br>
* 1、在统一的浏览器大小下运行用例,可以比较容易的跟一些基于图像比对的工具进行结合
* ,提升测试的灵活性及普遍适用性。比如可以跟sikuli结合,使用sikuli操作flash;<br>
* 2、在不同的浏览器大小下访问测试站点,对测试页面截图并保存,然后观察或使用图像比对工具对被测页面的前端样式进行评测。
* 比如可以将浏览器设置成移动端大小(320x480),然后访问移动站点,对其样式进行评估;<br>
* */
public void setBrowserSize(int width, int height) {
driver.manage().window().setSize(new Dimension(width, height));
} /**
* get方法包装
* */
public void get(String url) {
driver.get(url);
logger.info("打开测试页面:[" + url + "]");
} /**
* close方法包装
* */
public void close() {
driver.close();
} /**
* 退出
* */
public void quit() {
driver.quit();
} /**
* 刷新方法包装
* */
public void refresh() {
driver.navigate().refresh();
logger.info("页面刷新成功!");
} /**
* 后退方法包装
* */
public void back() {
driver.navigate().back();
} /**
* 前进方法包装
* */
public void forward() {
driver.navigate().forward();
} /**
* 获得页面的标题
* */
public String getTitle() {
return driver.getTitle();
} /**
* 等待alert出现
* */
public Alert switchToPromptedAlertAfterWait(long waitMillisecondsForAlert) throws NoAlertPresentException {
final int ONE_ROUND_WAIT = 200;
NoAlertPresentException lastException = null; long endTime = System.currentTimeMillis() + waitMillisecondsForAlert; for (long i = 0; i < waitMillisecondsForAlert; i += ONE_ROUND_WAIT) { try {
Alert alert = driver.switchTo().alert();
return alert;
} catch (NoAlertPresentException e) {
lastException = e;
}
pause(ONE_ROUND_WAIT); if (System.currentTimeMillis() > endTime) {
break;
}
}
throw lastException;
} /**
* @Description 对于windows GUI弹出框,要求输入用户名和密码时,
* seleniumm不能直接操作,需要借助http://modifyusername:modifypassword@yoururl 这种方法
*
* */
public void loginOnWinGUI(String username, String password, String url) {
driver.get(username + ":" + password + "@" + url);
} //============================== 对窗口进行操作 ================================== //------------------------------ 查找元素 -------------------------------------
/**
* 包装查找元素的方法 element
* */
public WebElement findElementBy(By by) {
return driver.findElement(by);
} /**
* 包装查找元素的方法 elements
* */
public List<WebElement> findElementsBy(By by) {
return driver.findElements(by);
} /**
* 这是一堆相同的elements中 选择 其中方的 一个 然后在这个选定的中 继续定位
* */
public WebElement getOneElement(By bys, By by, int index) {
return findElementsBy(bys).get(index).findElement(by);
} //============================= 查找元素 ========================================= //--------------------------- 判断元素状态 ----------------------------------------
/**
* 判断文本是不是和需求要求的文本一致
* **/
public void isTextCorrect(String actual, String expected) {
try {
Assert.assertEquals(actual, expected);
} catch (AssertionError e) {
logger.error("期望的文字是 [" + expected + "] 但是找到了 [" + actual + "]");
Assert.fail("期望的文字是 [" + expected + "] 但是找到了 [" + actual + "]"); }
logger.info("找到了期望的文字: [" + expected + "]");
} /**
* 判断编辑框是不是可编辑
* */
public void isInputEdit(WebElement element) { } /** 检查元素是否可用 */
public boolean isEnabled(WebElement element) {
boolean isEnable = false;
if (element.isEnabled()) {
logger.info("The element: [" + getLocatorByElement(element, ">") + "] is enabled");
isEnable = true;
} else if (element.isDisplayed() == false) {
logger.warn("The element: [" + getLocatorByElement(element, ">") + "] is not enable");
isEnable = false;
}
return isEnable;
} /** 检查元素是否显示 */
public boolean isDisplayed(WebElement element) {
boolean isDisplay = false;
if (element.isDisplayed()) {
logger.info("The element: [" + getLocatorByElement(element, ">") + "] is displayed");
isDisplay = true;
} else if (element.isDisplayed() == false) {
logger.warn("The element: [" + getLocatorByElement(element, ">") + "] is not displayed"); isDisplay = false;
}
return isDisplay;
} /**检查元素是不是存在*/
public boolean isElementExist(By byElement){
try{
findElementBy(byElement);
return true;
}catch(NoSuchElementException nee){
return false;
}
} /** 检查元素是否被勾选 */
public boolean isSelected(WebElement element) {
boolean flag = false;
if (element.isSelected() == true) {
logger.info("The element: [" + getLocatorByElement(element, ">") + "] is selected");
flag = true;
} else if (element.isSelected() == false) {
logger.info("The element: [" + getLocatorByElement(element, ">") + "] is not selected");
flag = false;
}
return flag;
} /**
* 判断实际文本时候包含期望文本
*
* @param actual
* 实际文本
* @param expect
* 期望文本
*/
public void isContains(String actual, String expect) {
try {
Assert.assertTrue(actual.contains(expect));
} catch (AssertionError e) {
logger.error("The [" + actual + "] is not contains [" + expect + "]");
Assert.fail("The [" + actual + "] is not contains [" + expect + "]");
}
logger.info("The [" + actual + "] is contains [" + expect + "]");
} /** 检查checkbox是不是勾选 */
public boolean isCheckboxSelected(By elementLocator) {
if (findElementBy(elementLocator).isSelected() == true) {
logger.info("CheckBox: " + getLocatorByElement(findElementBy(elementLocator), ">") + " 被勾选");
return true;
} else
logger.info("CheckBox: " + getLocatorByElement(findElementBy(elementLocator), ">") + " 没有被勾选");
return false; } /**
* 在给定的时间内去查找元素,如果没找到则超时,抛出异常
* */
public void waitForElementToLoad(int timeOut, final By By) {
logger.info("开始查找元素[" + By + "]");
try {
(new WebDriverWait(driver, timeOut)).until(new ExpectedCondition<Boolean>() { public Boolean apply(WebDriver driver) {
WebElement element = driver.findElement(By);
return element.isDisplayed();
}
});
} catch (TimeoutException e) {
logger.error("超时!! " + timeOut + " 秒之后还没找到元素 [" + By + "]");
Assert.fail("超时!! " + timeOut + " 秒之后还没找到元素 [" + By + "]"); }
logger.info("找到了元素 [" + By + "]");
} /**
* pageLoadTimeout。页面加载时的超时时间。因为webdriver会等页面加载完毕在进行后面的操作,
* 所以如果页面在这个超时时间内没有加载完成,那么webdriver就会抛出异常
*/
public void waitForPageLoading(int pageLoadTime) {
driver.manage().timeouts().pageLoadTimeout(pageLoadTime, TimeUnit.SECONDS);
}
//========================== 判断元素状态 ======================================= //-------------------------- 对元素操作 ----------------------------------------
/**
* 获得元素的文本
* */
public String getText(By elementLocator) {
return driver.findElement(elementLocator).getText().trim();
} /**
* 获得当前select选择的值
* */
public List<WebElement> getCurrentSelectValue(By by){
List<WebElement> options = null;
Select s = new Select(driver.findElement(by));
options = s.getAllSelectedOptions();
return options;
} /**
* 获得输入框的值 这个方法 是针对某些input输入框 没有value属性,但是又想取得input的 值得方法
* */
public String getInputValue(String chose,String choseValue) {
String value = null;
switch(chose.toLowerCase()){
case "name":
String jsName = "return document.getElementsByName('"+choseValue+"')[0].value;"; //把JS执行的值 返回出去
value = (String)((JavascriptExecutor) driver).executeScript(jsName);
break; case "id":
String jsId = "return document.getElementById('"+choseValue+"').value;"; //把JS执行的值 返回出去
value = (String)((JavascriptExecutor) driver).executeScript(jsId);
break; default:
logger.error("未定义的chose:"+chose);
Assert.fail("未定义的chose:"+chose);
}
return value; } /** 获得CSS value */
public String getCSSValue(WebElement e, String key) { return e.getCssValue(key);
} /**
* 获得元素 属性的文本
* */
public String getAttributeText(By elementLocator, String attribute) {
return driver.findElement(elementLocator).getAttribute(attribute).trim();
} /** 根据元素来获取此元素的定位值 */
public String getLocatorByElement(WebElement element, String expectText) {
String text = element.toString();
String expect = null;
try {
expect = text.substring(text.indexOf(expectText) + 1, text.length() - 1);
} catch (Exception e) {
e.printStackTrace();
logger.error("failed to find the string [" + expectText + "]");
}
return expect;
} /**
* 包装点击操作
* */
public void click(By byElement) { try {
clickTheClickable(byElement, System.currentTimeMillis(), 2500);
} catch (StaleElementReferenceException e) {
logger.error("The element you clicked:[" + byElement + "] is no longer exist!");
Assert.fail("The element you clicked:[" + byElement + "] is no longer exist!");
} catch (Exception e) {
logger.error("Failed to click element [" + byElement + "]");
Assert.fail("Failed to click element [" + byElement + "]",e);
}
logger.info("点击元素 [" + byElement + "]");
} /**
* 包装清除操作
* */
public void clear(By byElement) {
try {
findElementBy(byElement).clear();
} catch (Exception e) {
logger.error("清除元素 [" + byElement + "] 上的内容失败!");
}
logger.info("清除元素 [" + byElement + "]上的内容");
} /**
* 向输入框输入内容
* */
public void type(By byElement, String key) {
try {
findElementBy(byElement).sendKeys(key);
} catch (Exception e) {
e.printStackTrace();
logger.error("输入 [" + key + "] 到 元素[" + byElement + "]失败");
Assert.fail("输入 [" + key + "] 到 元素[" + byElement + "]失败");
}
logger.info("输入:[" + key + "] 到 [" + byElement + "]");
} /**
* 模拟键盘操作的,比如Ctrl+A,Ctrl+C等 参数详解:<br>
* 1、WebElement element - 要被操作的元素 <br>
* 2、Keys key- 键盘上的功能键 比如ctrl ,alt等 <br>
* 3、String keyword - 键盘上的字母
* */
public void pressKeysOnKeyboard(WebElement element, Keys key, String keyword) { element.sendKeys(Keys.chord(key, keyword));
} /**
* 切换frame - 根据String类型(frame名字)
* */
public void inFrame(String frameId) {
driver.switchTo().frame(frameId);
} /**
* 切换frame - 根据frame在当前页面中的顺序来定位
* */
public void inFrame(int frameNum) {
driver.switchTo().frame(frameNum);
} /**
* 切换frame - 根据页面元素定位
* */
public void switchFrame(WebElement element) {
try {
logger.info("正在跳进frame:[" + getLocatorByElement(element, ">") + "]");
driver.switchTo().frame(element);
} catch (Exception e) {
logger.info("跳进frame: [" + getLocatorByElement(element, ">") + "] 失败");
Assert.fail("跳进frame: [" + getLocatorByElement(element, ">") + "] 失败");
}
logger.info("进入frame: [" + getLocatorByElement(element, ">") +"]成功 ");
} /** 跳出frame */
public void outFrame() {
driver.switchTo().defaultContent();
} /**
* 选择下拉选项 -根据value
* */
public void selectByValue(By by, String value) {
Select s = new Select(driver.findElement(by));
s.selectByValue(value);
} /**
* 选择下拉选项 -根据index角标
* */
public void selectByIndex(By by, int index) {
Select s = new Select(driver.findElement(by));
s.selectByIndex(index);
} /**
* 选择下拉选项 -根据文本内容
* */
public void selectByText(By by, String text) {
Select s = new Select(driver.findElement(by));
s.selectByVisibleText(text);
} /**
* 执行JavaScript 方法
* */
public void executeJS(String js) {
((JavascriptExecutor) driver).executeScript(js);
logger.info("执行JavaScript语句:[" + js + "]");
} /**
* 执行JavaScript 方法和对象
* 用法:seleniumUtil.executeJS("arguments[0].click();", seleniumUtil.findElementBy(MyOrdersPage.MOP_TAB_ORDERCLOSE));
* */
public void executeJS(String js, Object... args) {
((JavascriptExecutor) driver).executeScript(js, args);
logger.info("执行JavaScript语句:[" + js + "]");
} /**
* 包装selenium模拟鼠标操作 - 鼠标移动到指定元素
* */
public void mouseMoveToElement(By by) {
Actions builder = new Actions(driver);
Actions mouse = builder.moveToElement(driver.findElement(by));
mouse.perform();
} /**
* 包装selenium模拟鼠标操作 - 鼠标移动到指定元素
* */
public void mouseMoveToElement(WebElement element) {
Actions builder = new Actions(driver);
Actions mouse = builder.moveToElement(element);
mouse.perform();
} /**
* 包装selenium模拟鼠标操作 - 鼠标右击
* */
public void mouseRightClick(By element) {
Actions builder = new Actions(driver);
Actions mouse = builder.contextClick(findElementBy(element));
mouse.perform();
} /**
* 上传文件,需要点击弹出上传照片的窗口才行
*
* @param brower
* 使用的浏览器名称
* @param file
* 需要上传的文件及文件名
*/
public void handleUpload(String browser, File file) {
String filePath = file.getAbsolutePath();
String executeFile = "res/script/autoit/Upload.exe";
String cmd = "\"" + executeFile + "\"" + " " + "\"" + browser + "\"" + " " + "\"" + filePath + "\"";
try {
Process p = Runtime.getRuntime().exec(cmd);
p.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
// ===================== 对元素进行操作 ============================ /**
* 添加cookies,做自动登陆的必要方法
* */
public void addCookies(int sleepTime) {
pause(sleepTime);
Set<Cookie> cookies = driver.manage().getCookies();
for (Cookie c : cookies) {
System.out.println(c.getName() + "->" + c.getValue());
if (c.getName().equals("logisticSessionid")) {
Cookie cook = new Cookie(c.getName(), c.getValue());
driver.manage().addCookie(cook);
System.out.println(c.getName() + "->" + c.getValue());
System.out.println("添加成功");
} else {
System.out.println("没有找到logisticSessionid");
} } } // webdriver中可以设置很多的超时时间
/** implicitlyWait。识别对象时的超时时间。过了这个时间如果对象还没找到的话就会抛出NoSuchElement异常 */
public void implicitlyWait(int timeOut) {
driver.manage().timeouts().implicitlyWait(timeOut, TimeUnit.SECONDS);
} /** setScriptTimeout。异步脚本的超时时间。webdriver可以异步执行脚本,这个是设置异步执行脚本脚本返回结果的超时时间 */
public void setScriptTimeout(int timeOut) {
driver.manage().timeouts().setScriptTimeout(timeOut, TimeUnit.SECONDS);
} /** 获得屏幕的分辨率 - 宽 */
public static double getScreenWidth() {
return java.awt.Toolkit.getDefaultToolkit().getScreenSize().getWidth();
} /**
* 暂停当前用例的执行,暂停的时间为:sleepTime
* */
public void pause(int sleepTime) {
if (sleepTime <= 0) {
return;
}
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
} /** 不能点击时候重试点击操作 */
private void clickTheClickable(By byElement, long startTime, int timeOut) throws Exception {
try {
findElementBy(byElement).click();
} catch (Exception e) {
if (System.currentTimeMillis() - startTime > timeOut) {
logger.warn(byElement+ " is unclickable");
throw new Exception(e);
} else {
Thread.sleep(500);
logger.warn(byElement + " is unclickable, try again");
clickTheClickable(byElement, startTime, timeOut);
}
}
} }

在pom.xml中添加代码如下:

        <dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-remote-driver</artifactId>
<version>LATEST</version>
</dependency>

5.2 使用log4j进行日志输出

LogConfiguration.java:配置了log的输出等级,以及如何显示,如何输出,输出的日志保存到哪里等配置,代码如下:

package com.demo.test.utils;
import java.util.Properties;
import org.apache.log4j.PropertyConfigurator;
/**
* @author young
* @decription 动态生成各个模块中的每条用例的日志,运行完成用例之后请到result/log目录下查看
* */
public class LogConfiguration { public static void initLog(String fileName){
//获取到模块名字
String founctionName = getFunctionName(fileName);
//声明日志文件存储路径以及文件名、格式
final String logFilePath = "./result/log/"+founctionName+"/"+fileName+".log";
Properties prop = new Properties();
//配置日志输出的格式
prop.setProperty("log4j.rootLogger","info, toConsole, toFile");
prop.setProperty("log4j.appender.file.encoding","UTF-8" );
prop.setProperty("log4j.appender.toConsole","org.apache.log4j.ConsoleAppender");
prop.setProperty("log4j.appender.toConsole.Target","System.out");
prop.setProperty("log4j.appender.toConsole.layout","org.apache.log4j.PatternLayout ");
prop.setProperty("log4j.appender.toConsole.layout.ConversionPattern","[%d{yyyy-MM-dd HH:mm:ss}] [%p] %m%n");
prop.setProperty("log4j.appender.toFile", "org.apache.log4j.DailyRollingFileAppender");
prop.setProperty("log4j.appender.toFile.file", logFilePath);
prop.setProperty("log4j.appender.toFile.append", "false");
prop.setProperty("log4j.appender.toFile.Threshold", "info");
prop.setProperty("log4j.appender.toFile.layout", "org.apache.log4j.PatternLayout");
prop.setProperty("log4j.appender.toFile.layout.ConversionPattern", "[%d{yyyy-MM-dd HH:mm:ss}] [%p] %m%n");
//使配置生效
PropertyConfigurator.configure(prop); } /**取得模块名字*/
public static String getFunctionName(String fileName){
String functionName = null;
int firstUndelineIndex = fileName.indexOf("_");
functionName = fileName.substring(0, firstUndelineIndex-4);
return functionName; } }

5.3 封装测试平台和测试浏览器选择工具类

SelectBrowser.java:(会调用PropertiesDataProvider.java,后面补上这个类),代码如下:

package com.demo.test.utils;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriverService;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.Assert;
import org.testng.ITestContext; /**
* @author young
* @decription 在不同的平台上选择对应的浏览器,系统平台程序自动判断是什么平台
* */
public class SelectBrowser {
static Logger logger = Logger.getLogger(SelectBrowser.class.getName()); public WebDriver selectExplorerByName(String browser, ITestContext context) {
Properties props = System.getProperties(); // 获得系统属性集
String currentPlatform = props.getProperty("os.name"); // 操作系统名称
logger.info("当前操作系统是:[" + currentPlatform + "]");
logger.info("启动测试浏览器:[" + browser + "]");
//从testNG的配置文件读取参数driverConfgFilePath的值
String driverConfgFilePath = context.getCurrentXmlTest().getParameter("driverConfgFilePath");
/** 声明好驱动的路径 */
String chromedriver_win = PropertiesDataProvider.getTestData(driverConfgFilePath, "chromedriver_win");
String chromedriver_linux = PropertiesDataProvider.getTestData(driverConfgFilePath, "chromedriver_linux");
String chromedriver_mac = PropertiesDataProvider.getTestData(driverConfgFilePath, "chromedriver_mac");
String ghostdriver_win = PropertiesDataProvider.getTestData(driverConfgFilePath, "ghostdriver_win");
String iedriver = PropertiesDataProvider.getTestData(driverConfgFilePath, "iedriver");
if (currentPlatform.toLowerCase().contains("win")) { //如果是windows平台 if (browser.equalsIgnoreCase("ie")) {
System.setProperty("webdriver.ie.driver", iedriver);
//IE的常规设置,便于执行自动化测试
DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();
ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
//返回ie浏览器对象
return new InternetExplorerDriver(ieCapabilities);
} else if (browser.equalsIgnoreCase("chrome")) {
System.setProperty("webdriver.chrome.driver", chromedriver_win);
//返回谷歌浏览器对象
return new ChromeDriver();
} else if (browser.equalsIgnoreCase("firefox")) {
//返回火狐浏览器对象
return new FirefoxDriver(); } else if(browser.equalsIgnoreCase("ghost")){
DesiredCapabilities ghostCapabilities = new DesiredCapabilities();
ghostCapabilities.setJavascriptEnabled(true);
ghostCapabilities.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY, ghostdriver_win);
//返回ghost对象
return new PhantomJSDriver(ghostCapabilities); }else { logger.error("The [" + browser + "]" + " explorer is not applicable for [" + currentPlatform + "] OS");
Assert.fail("The [" + browser + "]" + " explorer does not apply to [" + currentPlatform + "] OS"); } } else if (currentPlatform.toLowerCase().contains("linux")) { //如果是linux平台 if (browser.equalsIgnoreCase("chrome")) {
System.setProperty("webdriver.chrome.driver", chromedriver_linux);
return new ChromeDriver(); } else if (browser.equalsIgnoreCase("firefox")) {
return new FirefoxDriver();
} else {
logger.error("The [" + browser + "]" + " explorer does not apply to [" + currentPlatform + "] OS");
Assert.fail("The [" + browser + "]" + " explorer does not apply to [" + currentPlatform + "] OS");
} } else if (currentPlatform.toLowerCase().contains("mac")) { //如果是mac平台
if (browser.equalsIgnoreCase("chrome")) {
System.setProperty("webdriver.chrome.driver", chromedriver_mac);
return new ChromeDriver();
} else if (browser.equalsIgnoreCase("firefox")) {
return new FirefoxDriver();
} else {
logger.error("The [" + browser + "]" + " explorer does not apply to [" + currentPlatform + "] OS");
Assert.fail("The [" + browser + "]" + " explorer does not apply to [" + currentPlatform + "] OS");
} } else
logger.error("The [" + currentPlatform + "] is not supported for this automation frame,please change the OS(Windows,MAC or LINUX)"); Assert.fail("The [" + currentPlatform + "] is not supported for this automation frame,please change the OS(Windows,MAC or LINUX)");
return null;
}
}

在pom.xml中添加代码如下:

        <dependency>
<groupId>com.codeborne</groupId>
<artifactId>phantomjsdriver</artifactId>
<version>1.2.1</version>
</dependency>

在testng.xml中添加代码如下:

    <parameter name="driverConfgFilePath" value="config/driver.properties" />

5.4 根据key读取属性文件里面的value值

PropertiesDataProvider.java:从.properties文件中读取相关测试数据,代码如下:

package com.demo.test.utils;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration; /**
* @Desription 从.properties文件中读取相关测试数据<br>
*
* */
public class PropertiesDataProvider { public static String getTestData(String configFilePath, String key) {
Configuration config = null;
try {
config = new PropertiesConfiguration(configFilePath);
} catch (ConfigurationException e) {
e.printStackTrace();
}
return String.valueOf(config.getProperty(key)); }
}

driver.properties代码如下:

# define the browser driver here
#chrome driver for windows
chromedriver_win =res/driver/chrome/win/chromedriver.exe
#chrome driver for linux
chomedriver_linux =res/driver/chrome/linux/chromedriver
#chrome driver for mac os
chomedriver_mac = res/driver/chrome/mac/chromedriver
#chrome driver for IE
iedriver = res/driver/ie/iedriver.exe
#ghost driver for windows
ghostdriver_win =res/driver/ghostdriver/phantomjs.exe

5.5 arrow插件解析

5.5.1 ConfigReader.java

代码如下:

package com.demo.test.plugins.arrow.utils;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
import org.testng.log4testng.Logger; public class ConfigReader {
private static Logger logger = Logger.getLogger(ConfigReader.class);
private static ConfigReader cr;
private int retryCount = 0;
private String sourceCodeDir = "src";
private String sourceCodeEncoding = "UTF-8";
private static final String RETRYCOUNT = "retrycount";
private static final String SOURCEDIR = "sourcecodedir";
private static final String SOURCEENCODING = "sourcecodeencoding";
private static final String CONFIGFILE = "./config/config.properties"; private ConfigReader() {
readConfig(CONFIGFILE);
} public static ConfigReader getInstance() {
if (cr == null) {
cr = new ConfigReader();
}
return cr;
} private void readConfig(String fileName) {
Properties properties = getConfig(fileName);
if (properties != null) {
String sRetryCount = null; Enumeration<?> en = properties.propertyNames();
while (en.hasMoreElements()) {
String key = (String) en.nextElement();
if (key.toLowerCase().equals(RETRYCOUNT)) {
sRetryCount = properties.getProperty(key);
}
if (key.toLowerCase().equals(SOURCEDIR)) {
sourceCodeDir = properties.getProperty(key);
}
if (key.toLowerCase().equals(SOURCEENCODING)) {
sourceCodeEncoding = properties.getProperty(key);
}
}
if (sRetryCount != null) {
sRetryCount = sRetryCount.trim();
try {
retryCount = Integer.parseInt(sRetryCount);
} catch (final NumberFormatException e) {
throw new NumberFormatException("Parse " + RETRYCOUNT + " [" + sRetryCount + "] from String to Int Exception");
}
}
}
} public int getRetryCount() {
return this.retryCount;
} public String getSourceCodeDir() {
return this.sourceCodeDir;
} public String getSrouceCodeEncoding() {
return this.sourceCodeEncoding;
} /**
*
* @param propertyFileName
*
* @return
*/
private Properties getConfig(String propertyFileName) {
Properties properties = new Properties();
try {
properties.load(new FileInputStream(propertyFileName));
} catch (FileNotFoundException e) {
properties = null;
logger.warn("FileNotFoundException:" + propertyFileName);
} catch (IOException e) {
properties = null;
logger.warn("IOException:" + propertyFileName);
}
return properties;
}
}

config.properties代码如下:

retrycount=0
sourcecodedir=src/com/demo/test/testcases
sourcecodeencoding=UTF-8

5.5.2 负责监听测试运行状态和结果

TestResultListener.java代码如下:

package com.demo.test.plugins.arrow;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set; import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.TestListenerAdapter; /**
* @author netease_arrow 描述:来自网易的截图插件
*
*/
public class TestResultListener extends TestListenerAdapter { private static Logger logger = Logger.getLogger(TestResultListener.class.getName());
protected ITestContext testContext = null; // 这里也是新加的
String browser = null; @Override
public void onStart(ITestContext testContext) { // 这里也是新加的,用于对context进行统一
this.testContext = testContext;
browser = String.valueOf(testContext.getCurrentXmlTest().getParameter("browserName"));
super.onStart(testContext);
} @Override
public void onTestFailure(ITestResult tr) {
super.onTestFailure(tr);
logger.warn(tr.getName() + " 测试用例执行失败!");
WebDriver webDriver = (WebDriver) testContext.getAttribute("SELENIUM_DRIVER"); // 这里就是取driver
saveScreenShot(tr, webDriver,browser);
} @Override
public void onTestSkipped(ITestResult tr) {
super.onTestSkipped(tr);
WebDriver webDriver = (WebDriver) testContext.getAttribute("SELENIUM_DRIVER");
logger.warn(tr.getName() + " 测试用例由于某些原因被跳过!");
saveScreenShot(tr, webDriver,browser); } @Override
public void onTestSuccess(ITestResult tr) {
super.onTestSuccess(tr);
logger.info(tr.getName() + " 测试用例执行成功!");
} @Override
public void onTestStart(ITestResult tr) {
super.onTestStart(tr);
logger.info(tr.getName() + " 测试用例开始执行!");
} @Override
public void onFinish(ITestContext testContext) {
super.onFinish(testContext); // List of test results which we will delete later
ArrayList<ITestResult> testsToBeRemoved = new ArrayList<ITestResult>();
// collect all id's from passed test
Set<Integer> passedTestIds = new HashSet<Integer>();
for (ITestResult passedTest : testContext.getPassedTests().getAllResults()) {
logger.info("执行成功的用例 = " + passedTest.getName());
passedTestIds.add(getId(passedTest));
} // Eliminate the repeat methods
Set<Integer> skipTestIds = new HashSet<Integer>();
for (ITestResult skipTest : testContext.getSkippedTests().getAllResults()) {
logger.info("被跳过的用例 = " + skipTest.getName());
// id = class + method + dataprovider
int skipTestId = getId(skipTest); if (skipTestIds.contains(skipTestId) || passedTestIds.contains(skipTestId)) {
testsToBeRemoved.add(skipTest);
} else {
skipTestIds.add(skipTestId);
}
} // Eliminate the repeat failed methods
Set<Integer> failedTestIds = new HashSet<Integer>();
for (ITestResult failedTest : testContext.getFailedTests().getAllResults()) {
logger.info("执行失败的用例 = " + failedTest.getName());
// id = class + method + dataprovider
int failedTestId = getId(failedTest); // if we saw this test as a failed test before we mark as to be
// deleted
// or delete this failed test if there is at least one passed
// version
if (failedTestIds.contains(failedTestId) || passedTestIds.contains(failedTestId) || skipTestIds.contains(failedTestId)) {
testsToBeRemoved.add(failedTest);
} else {
failedTestIds.add(failedTestId);
}
} // finally delete all tests that are marked
for (Iterator<ITestResult> iterator = testContext.getFailedTests().getAllResults().iterator(); iterator.hasNext();) {
ITestResult testResult = iterator.next();
if (testsToBeRemoved.contains(testResult)) {
logger.info("移除重复失败的用例 = " + testResult.getName());
iterator.remove();
}
} } private int getId(ITestResult result) {
int id = result.getTestClass().getName().hashCode();
id = id + result.getMethod().getMethodName().hashCode();
id = id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0);
return id;
} private void saveScreenShot(ITestResult tr, WebDriver driver, String browser) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
String mDateTime = formatter.format(new Date());
String fileName = mDateTime + "_" + tr.getName();
String filePath = "";
try {
// 这里可以调用不同框架的截图功能
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
filePath = "result/screenshot/"+ fileName + ".jpg";
File destFile = new File(filePath);
FileUtils.copyFile(screenshot, destFile);
logger.info("["+fileName + "] 截图成功,保存在:" + "[ " + filePath + " ]"); } catch (Exception e) {
filePath = "["+fileName+"]" + " ,截图失败,原因:" + e.getMessage();
logger.error(filePath);
} if (!"".equals(filePath)) {
Reporter.setCurrentTestResult(tr);
Reporter.log(filePath);
// 把截图写入到Html报告中方便查看
Reporter.log("<img src=\"../../" + filePath + "\"/>"); }
} }

5.5.3 负责失败的用例重跑的监听器

RetryListener.java: (会调用TestngRetry.java,,后面补上这个类),代码如下:

package com.demo.test.plugins.arrow;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.testng.IAnnotationTransformer;
import org.testng.IRetryAnalyzer;
import org.testng.annotations.ITestAnnotation; public class RetryListener implements IAnnotationTransformer { @SuppressWarnings("rawtypes")
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
IRetryAnalyzer retry = annotation.getRetryAnalyzer();
if (retry == null) {
annotation.setRetryAnalyzer(TestngRetry.class);
}
}
}

TestngRetry.java代码如下:

package com.demo.test.plugins.arrow;
import org.apache.log4j.Logger;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
import org.testng.Reporter;
import com.demo.test.plugins.arrow.utils.ConfigReader;
import com.demo.test.utils.LogConfiguration; public class TestngRetry implements IRetryAnalyzer {
static {
LogConfiguration.initLog("TestngRetryPage_");
}
private static Logger logger = Logger.getLogger(TestngRetry.class);
private int retryCount = 1;
private static int maxRetryCount; static {
ConfigReader config = ConfigReader.getInstance();
maxRetryCount = config.getRetryCount();
logger.info("RetryCount=" + maxRetryCount);
logger.info("SourceDir=" + config.getSourceCodeDir());
logger.info("SourceEncoding=" + config.getSrouceCodeEncoding());
} public boolean retry(ITestResult result) {
if (retryCount <= maxRetryCount) {
String message = "Retry for: [" + result.getName() + "] on class [" + result.getTestClass().getName() + "] retry " + retryCount + " times";
logger.info(message);
Reporter.setCurrentTestResult(result);
Reporter.log("RunCount=" + (retryCount + 1));
retryCount++;
return true;
}
return false; } public static int getMaxRetryCount() {
return maxRetryCount;
} public int getRetryCount() {
return retryCount;
}
}

5.5.4 负责生成测试报告的监听器

PowerEmailableReporter.java:代码如下:

package com.demo.test.plugins.arrow;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set; import org.testng.IInvokedMethod;
import org.testng.IReporter;
import org.testng.IResultMap;
import org.testng.ISuite;
import org.testng.ISuiteResult;
import org.testng.ITestClass;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.collections.Lists;
import org.testng.internal.Utils;
import org.testng.log4testng.Logger;
import org.testng.xml.XmlSuite; import com.demo.test.plugins.arrow.utils.ConfigReader;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod; /**
* Reported designed to render self-contained HTML top down view of a testing
* suite.
*/
public class PowerEmailableReporter implements IReporter {
private static final Logger L = Logger.getLogger(PowerEmailableReporter.class); // ~ Instance fields ------------------------------------------------------ private PrintWriter m_out; private int m_row; private Integer m_testIndex; private Set<Integer> testIds = new HashSet<Integer>();
private List<Integer> allRunTestIds = new ArrayList<Integer>();
private JavaDocBuilder builder = new JavaDocBuilder(); // ~ Methods -------------------------------------------------------------- /** Creates summary of the run */
public void generateReport(List<XmlSuite> xml, List<ISuite> suites, String outdir) {
try {
m_out = createWriter(outdir);
} catch (IOException e) {
L.error("output file", e);
return;
}
ConfigReader cr = ConfigReader.getInstance();
builder.setEncoding(cr.getSrouceCodeEncoding());
builder.addSourceTree(new File(cr.getSourceCodeDir()));
startHtml(m_out);
generateSuiteSummaryReport(suites);
testIds.clear();
generateMethodSummaryReport(suites);
testIds.clear();
generateMethodDetailReport(suites);
testIds.clear();
endHtml(m_out);
m_out.flush();
m_out.close();
} protected PrintWriter createWriter(String outdir) throws IOException {
new File(outdir).mkdirs();
return new PrintWriter(new BufferedWriter(new FileWriter(new File(outdir, "power-emailable-report.html"))));
} /**
* Creates a table showing the highlights of each test method with links to
* the method details
*/
protected void generateMethodSummaryReport(List<ISuite> suites) {
startResultSummaryTable("methodOverview");
int testIndex = 1;
for (ISuite suite : suites) {
if (suites.size() > 1) {
titleRow(suite.getName(), 5);
}
Map<String, ISuiteResult> r = suite.getResults();
for (ISuiteResult r2 : r.values()) {
ITestContext testContext = r2.getTestContext();
String testName = testContext.getName();
m_testIndex = testIndex; resultSummary(suite, testContext.getSkippedConfigurations(), testName, "skipped", " (configuration methods)");
resultSummary(suite, testContext.getSkippedTests(), testName, "skipped", "");
resultSummary(suite, testContext.getFailedConfigurations(), testName, "failed", " (configuration methods)");
resultSummary(suite, testContext.getFailedTests(), testName, "failed", "");
resultSummary(suite, testContext.getPassedTests(), testName, "passed", ""); testIndex++;
}
}
m_out.println("</table>");
} /** Creates a section showing known results for each method */
protected void generateMethodDetailReport(List<ISuite> suites) {
for (ISuite suite : suites) {
Map<String, ISuiteResult> r = suite.getResults();
for (ISuiteResult r2 : r.values()) {
ITestContext testContext = r2.getTestContext();
if (r.values().size() > 0) {
m_out.println("<h1>" + testContext.getName() + "</h1>");
}
resultDetail(testContext.getFailedConfigurations());
resultDetail(testContext.getFailedTests());
resultDetail(testContext.getSkippedConfigurations());
resultDetail(testContext.getSkippedTests());
resultDetail(testContext.getPassedTests());
}
}
} /**
* @param tests
*/
private void resultSummary(ISuite suite, IResultMap tests, String testname, String style, String details) {
if (tests.getAllResults().size() > 0) {
StringBuffer buff = new StringBuffer();
String lastClassName = "";
int mq = 0;
int cq = 0;
Map<String, Integer> methods = new HashMap<String, Integer>();
Set<String> setMethods = new HashSet<String>();
for (ITestNGMethod method : getMethodSet(tests, suite)) {
m_row += 1; ITestClass testClass = method.getTestClass();
String className = testClass.getName();
if (mq == 0) {
String id = (m_testIndex == null ? null : "t" + Integer.toString(m_testIndex));
titleRow(testname + " — " + style + details, 5, id);
m_testIndex = null;
}
if (!className.equalsIgnoreCase(lastClassName)) {
if (mq > 0) {
cq += 1;
m_out.print("<tr class=\"" + style + (cq % 2 == 0 ? "even" : "odd") + "\">" + "<td");
if (mq > 1) {
m_out.print(" rowspan=\"" + mq + "\"");
}
m_out.println(">" + lastClassName + "</td>" + buff);
}
mq = 0;
buff.setLength(0);
lastClassName = className;
}
Set<ITestResult> resultSet = tests.getResults(method);
long end = Long.MIN_VALUE;
long start = Long.MAX_VALUE;
for (ITestResult testResult : tests.getResults(method)) {
if (testResult.getEndMillis() > end) {
end = testResult.getEndMillis();
}
if (testResult.getStartMillis() < start) {
start = testResult.getStartMillis();
}
}
mq += 1;
if (mq > 1) {
buff.append("<tr class=\"" + style + (cq % 2 == 0 ? "odd" : "even") + "\">");
}
String description = method.getDescription();
String testInstanceName = resultSet.toArray(new ITestResult[] {})[0].getTestName();
// Calculate each test run times, the result shown in the html
// report.
ITestResult[] results = resultSet.toArray(new ITestResult[] {});
String methodName = method.getMethodName();
if (setMethods.contains(methodName)) {
methods.put(methodName, methods.get(methodName) + 1);
} else {
setMethods.add(methodName);
methods.put(methodName, 0);
}
String parameterString = "";
int count = 0; ITestResult result = null;
if (results.length > methods.get(methodName)) {
result = results[methods.get(methodName)];
int testId = getId(result); for (Integer id : allRunTestIds) {
if (id.intValue() == testId)
count++;
}
Object[] parameters = result.getParameters(); boolean hasParameters = parameters != null && parameters.length > 0;
if (hasParameters) {
for (Object p : parameters) {
parameterString = parameterString + Utils.escapeHtml(p.toString()) + " ";
}
}
} int methodId = method.getTestClass().getName().hashCode();
methodId = methodId + method.getMethodName().hashCode();
if (result != null)
methodId = methodId + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0); buff.append("<td><a href=\"#m" + methodId + "\">" + qualifiedName(method) + " " + (description != null && description.length() > 0 ? "(\"" + description + "\")" : "") + "</a>" + (null == testInstanceName ? "" : "<br>(" + testInstanceName + ")") + "</td><td>" + this.getAuthors(className, method) + "</td><td class=\"numi\">" + resultSet.size() + "</td>" + "<td>" + (count == 0 ? "" : count) + "</td>" + "<td>" + parameterString + "</td>" + "<td>" + start + "</td>" + "<td class=\"numi\">" + (end - start) + "</td>" + "</tr>");
}
if (mq > 0) {
cq += 1;
m_out.print("<tr class=\"" + style + (cq % 2 == 0 ? "even" : "odd") + "\">" + "<td");
if (mq > 1) {
m_out.print(" rowspan=\"" + mq + "\"");
}
m_out.println(">" + lastClassName + "</td>" + buff);
}
}
} /** Starts and defines columns result summary table */
private void startResultSummaryTable(String style) {
tableStart(style, "summary");
m_out.println("<tr><th>Class</th><th>Method</th><th>Authors</th><th># of<br/>Scenarios</th><th>Running Counts</th>" + "<th>Parameters</th><th>Start</th><th>Time<br/>(ms)</th></tr>");
m_row = 0;
} private String qualifiedName(ITestNGMethod method) {
StringBuilder addon = new StringBuilder();
String[] groups = method.getGroups();
int length = groups.length;
if (length > 0 && !"basic".equalsIgnoreCase(groups[0])) {
addon.append("(");
for (int i = 0; i < length; i++) {
if (i > 0) {
addon.append(", ");
}
addon.append(groups[i]);
}
addon.append(")");
} return "<b>" + method.getMethodName() + "</b> " + addon;
} private void resultDetail(IResultMap tests) {
for (ITestResult result : tests.getAllResults()) {
ITestNGMethod method = result.getMethod(); int methodId = getId(result); String cname = method.getTestClass().getName();
m_out.println("<h2 id=\"m" + methodId + "\" name=\"m" + methodId + "\" >" + cname + ":" + method.getMethodName() + "</h2>");
Set<ITestResult> resultSet = tests.getResults(method);
generateForResult(result, method, resultSet.size());
m_out.println("<p class=\"totop\"><a href=\"#summary\">back to summary</a></p>"); }
} private void generateForResult(ITestResult ans, ITestNGMethod method, int resultSetSize) {
Object[] parameters = ans.getParameters();
boolean hasParameters = parameters != null && parameters.length > 0;
if (hasParameters) {
tableStart("result", null);
m_out.print("<tr class=\"param\">");
for (int x = 1; x <= parameters.length; x++) {
m_out.print("<th>Parameter #" + x + "</th>");
}
m_out.println("</tr>");
m_out.print("<tr class=\"param stripe\">");
for (Object p : parameters) {
m_out.println("<td>" + Utils.escapeHtml(p.toString()) + "</td>");
}
m_out.println("</tr>");
}
List<String> msgs = Reporter.getOutput(ans);
boolean hasReporterOutput = msgs.size() > 0;
Throwable exception = ans.getThrowable();
boolean hasThrowable = exception != null;
if (hasReporterOutput || hasThrowable) {
if (hasParameters) {
m_out.print("<tr><td");
if (parameters.length > 1) {
m_out.print(" colspan=\"" + parameters.length + "\"");
}
m_out.println(">");
} else {
m_out.println("<div>");
}
if (hasReporterOutput) {
if (hasThrowable) {
m_out.println("<h3>Test Messages</h3>");
}
for (String line : msgs) {
m_out.println(line + "<br/>");
}
}
if (hasThrowable) {
boolean wantsMinimalOutput = ans.getStatus() == ITestResult.SUCCESS;
if (hasReporterOutput) {
m_out.println("<h3>" + (wantsMinimalOutput ? "Expected Exception" : "Failure") + "</h3>");
}
generateExceptionReport(exception, method);
}
if (hasParameters) {
m_out.println("</td></tr>");
} else {
m_out.println("</div>");
}
}
if (hasParameters) {
m_out.println("</table>");
}
} protected void generateExceptionReport(Throwable exception, ITestNGMethod method) {
m_out.print("<div class=\"stacktrace\">");
m_out.print(Utils.stackTrace(exception, true)[0]);
m_out.println("</div>");
} /**
* Since the methods will be sorted chronologically, we want to return the
* ITestNGMethod from the invoked methods.
*/
private Collection<ITestNGMethod> getMethodSet(IResultMap tests, ISuite suite) {
List<IInvokedMethod> r = Lists.newArrayList();
List<IInvokedMethod> invokedMethods = suite.getAllInvokedMethods(); // Eliminate the repeat retry methods
for (IInvokedMethod im : invokedMethods) {
if (tests.getAllMethods().contains(im.getTestMethod())) {
int testId = getId(im.getTestResult());
if (!testIds.contains(testId)) {
testIds.add(testId);
r.add(im);
}
}
}
Arrays.sort(r.toArray(new IInvokedMethod[r.size()]), new TestSorter());
List<ITestNGMethod> result = Lists.newArrayList(); // Add all the invoked methods
for (IInvokedMethod m : r) {
result.add(m.getTestMethod());
} // Add all the methods that weren't invoked (e.g. skipped) that we
// haven't added yet
// for (ITestNGMethod m : tests.getAllMethods()) {
// if (!result.contains(m)) {
// result.add(m);
// }
// } for (ITestResult allResult : tests.getAllResults()) {
int testId = getId(allResult);
if (!testIds.contains(testId)) {
result.add(allResult.getMethod());
}
} return result;
} public void generateSuiteSummaryReport(List<ISuite> suites) {
tableStart("testOverview", null);
m_out.print("<tr>");
tableColumnStart("Test");
tableColumnStart("Methods<br/>Passed");
tableColumnStart("Scenarios<br/>Passed");
tableColumnStart("# skipped");
tableColumnStart("# failed");
tableColumnStart("Total<br/>Time");
tableColumnStart("Included<br/>Groups");
tableColumnStart("Excluded<br/>Groups");
m_out.println("</tr>");
NumberFormat formatter = new DecimalFormat("#,##0.0");
int qty_tests = 0;
int qty_pass_m = 0;
int qty_pass_s = 0;
int qty_skip = 0;
int qty_fail = 0;
long time_start = Long.MAX_VALUE;
long time_end = Long.MIN_VALUE;
m_testIndex = 1;
for (ISuite suite : suites) {
if (suites.size() > 1) {
titleRow(suite.getName(), 8);
}
Map<String, ISuiteResult> tests = suite.getResults();
for (ISuiteResult r : tests.values()) {
qty_tests += 1;
ITestContext overview = r.getTestContext();
startSummaryRow(overview.getName()); getAllTestIds(overview, suite);
int q = getMethodSet(overview.getPassedTests(), suite).size();
qty_pass_m += q;
summaryCell(q, Integer.MAX_VALUE);
q = overview.getPassedTests().size();
qty_pass_s += q;
summaryCell(q, Integer.MAX_VALUE); q = getMethodSet(overview.getSkippedTests(), suite).size();
qty_skip += q;
summaryCell(q, 0); q = getMethodSet(overview.getFailedTests(), suite).size();
qty_fail += q;
summaryCell(q, 0); time_start = Math.min(overview.getStartDate().getTime(), time_start);
time_end = Math.max(overview.getEndDate().getTime(), time_end);
summaryCell(formatter.format((overview.getEndDate().getTime() - overview.getStartDate().getTime()) / 1000.) + " seconds", true);
summaryCell(overview.getIncludedGroups());
summaryCell(overview.getExcludedGroups());
m_out.println("</tr>");
m_testIndex++;
}
}
if (qty_tests > 1) {
m_out.println("<tr class=\"total\"><td>Total</td>");
summaryCell(qty_pass_m, Integer.MAX_VALUE);
summaryCell(qty_pass_s, Integer.MAX_VALUE);
summaryCell(qty_skip, 0);
summaryCell(qty_fail, 0);
summaryCell(formatter.format((time_end - time_start) / 1000.) + " seconds", true);
m_out.println("<td colspan=\"2\">&nbsp;</td></tr>");
}
m_out.println("</table>");
} private void summaryCell(String[] val) {
StringBuffer b = new StringBuffer();
for (String v : val) {
b.append(v + " ");
}
summaryCell(b.toString(), true);
} private void summaryCell(String v, boolean isgood) {
m_out.print("<td class=\"numi" + (isgood ? "" : "_attn") + "\">" + v + "</td>");
} private void startSummaryRow(String label) {
m_row += 1;
m_out.print("<tr" + (m_row % 2 == 0 ? " class=\"stripe\"" : "") + "><td style=\"text-align:left;padding-right:2em\"><a href=\"#t" + m_testIndex + "\">" + label + "</a>" + "</td>");
} private void summaryCell(int v, int maxexpected) {
summaryCell(String.valueOf(v), v <= maxexpected);
} private void tableStart(String cssclass, String id) {
m_out.println("<table cellspacing=\"0\" cellpadding=\"0\"" + (cssclass != null ? " class=\"" + cssclass + "\"" : " style=\"padding-bottom:2em\"") + (id != null ? " id=\"" + id + "\"" : "") + ">");
m_row = 0;
} private void tableColumnStart(String label) {
m_out.print("<th>" + label + "</th>");
} private void titleRow(String label, int cq) {
titleRow(label, cq, null);
} private void titleRow(String label, int cq, String id) {
m_out.print("<tr");
if (id != null) {
m_out.print(" id=\"" + id + "\"");
}
m_out.println("><th colspan=\"" + cq + "\">" + label + "</th></tr>");
m_row = 0;
} /** Starts HTML stream */
protected void startHtml(PrintWriter out) {
out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">");
out.println("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
out.println("<head>");
out.println("<title>TestNG Report</title>");
out.println("<style type=\"text/css\">");
out.println("table {margin-bottom:10px;border-collapse:collapse;empty-cells:show}");
out.println("td,th {border:1px solid #009;padding:.25em .5em}");
out.println(".result th {vertical-align:bottom}");
out.println(".param th {padding-left:1em;padding-right:1em}");
out.println(".param td {padding-left:.5em;padding-right:2em}");
out.println(".stripe td,.stripe th {background-color: #E6EBF9}");
out.println(".numi,.numi_attn {text-align:right}");
out.println(".total td {font-weight:bold}");
out.println(".passedodd td {background-color: #0A0}");
out.println(".passedeven td {background-color: #3F3}");
out.println(".skippedodd td {background-color: #CCC}");
out.println(".skippedodd td {background-color: #DDD}");
out.println(".failedodd td,.numi_attn {background-color: #F33}");
out.println(".failedeven td,.stripe .numi_attn {background-color: #D00}");
out.println(".stacktrace {white-space:pre;font-family:monospace}");
out.println(".totop {font-size:85%;text-align:center;border-bottom:2px solid #000}");
out.println("</style>");
out.println("</head>");
out.println("<body>");
} /** Finishes HTML stream */
protected void endHtml(PrintWriter out) {
out.println("</body></html>");
} // ~ Inner Classes --------------------------------------------------------
/** Arranges methods by classname and method name */
private class TestSorter implements Comparator<IInvokedMethod> {
// ~ Methods
// ------------------------------------------------------------- /** Arranges methods by classname and method name */
public int compare(IInvokedMethod o1, IInvokedMethod o2) {
// System.out.println("Comparing " + o1.getMethodName() + " " +
// o1.getDate()
// + " and " + o2.getMethodName() + " " + o2.getDate());
return (int) (o1.getDate() - o2.getDate());
// int r = ((T) o1).getTestClass().getName().compareTo(((T)
// o2).getTestClass().getName());
// if (r == 0) {
// r = ((T) o1).getMethodName().compareTo(((T) o2).getMethodName());
// }
// return r;
}
} // ~ JavaDoc-specific Methods
// --------------------------------------------------------
/**
* Get ITestNGMethod author(s) string, or class author(s) if no method
* author is present. Default return value is "unknown".
*
* @param className
* @param method
* @return
*/
private String getAuthors(String className, ITestNGMethod method) {
JavaClass cls = builder.getClassByName(className);
DocletTag[] authors = cls.getTagsByName("author");
// get class authors as default author name
String allAuthors = "";
if (authors.length == 0) {
allAuthors = "unknown";
} else {
for (DocletTag author : authors) {
allAuthors += author.getValue() + " ";
}
}
// get method author name
JavaMethod[] mtds = cls.getMethods();
for (JavaMethod mtd : mtds) {
if (mtd.getName().equals(method.getMethodName())) {
authors = mtd.getTagsByName("author");
if (authors.length != 0) {
allAuthors = "";
for (DocletTag author : authors) {
allAuthors += author.getValue() + " ";
}
}
break;
}
}
return allAuthors.trim();
} /**
* Get comment string of Java class.
*
* @param className
* @return
*/
@SuppressWarnings("unused")
private String getClassComment(String className) {
JavaClass cls = builder.getClassByName(className);
return cls.getComment();
} /**
* Get ITestResult id by class + method + parameters hash code.
*
* @param result
* @return
*/
private int getId(ITestResult result) {
int id = result.getTestClass().getName().hashCode();
id = id + result.getMethod().getMethodName().hashCode();
id = id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0);
return id;
} /**
* Get All tests id by class + method + parameters hash code.
*
* @param context
* @param suite
*/
private void getAllTestIds(ITestContext context, ISuite suite) {
IResultMap passTests = context.getPassedTests();
IResultMap failTests = context.getFailedTests();
List<IInvokedMethod> invokedMethods = suite.getAllInvokedMethods();
for (IInvokedMethod im : invokedMethods) {
if (passTests.getAllMethods().contains(im.getTestMethod()) || failTests.getAllMethods().contains(im.getTestMethod())) {
int testId = getId(im.getTestResult());
// m_out.println("ALLtestid=" + testId);
allRunTestIds.add(testId);
}
}
}
}

在testng.xml中加入以下代码:

        <dependency>
<groupId>com.thoughtworks.qdox</groupId>
<artifactId>qdox</artifactId>
<version>1.12.1</version>
<scope>compile</scope>
</dependency>

5.6 用例的开头和结尾设计

BaseParpare.java:代码如下:

package com.demo.test.base;
/**
* @Description 测试开始 和 测试结束 的操作
*
* */
import java.io.IOException;
import java.util.Iterator;
import org.apache.log4j.Logger;
import org.testng.Assert;
import org.testng.ITestContext;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import com.demo.test.utils.LogConfiguration;
import com.demo.test.utils.SeleniumUtil;
public class BaseParpare {
//输出本页面日志 初始化
static Logger logger = Logger.getLogger(BaseParpare.class.getName());
protected SeleniumUtil seleniumUtil = null;
// 添加成员变量来获取beforeClass传入的context参数
protected ITestContext testContext = null;
protected String webUrl="";
protected int timeOut = 0; @BeforeClass
/**启动浏览器并打开测试页面*/
public void startTest(ITestContext context) {
LogConfiguration.initLog(this.getClass().getSimpleName());
seleniumUtil = new SeleniumUtil();
// 这里得到了context值
this.testContext = context;
//从testng.xml文件中获取浏览器的属性值
String browserName = context.getCurrentXmlTest().getParameter("browserName");
timeOut = Integer.valueOf(context.getCurrentXmlTest().getParameter("timeOut"));
webUrl = context.getCurrentXmlTest().getParameter("testurl"); try {
//启动浏览器launchBrowser方法可以自己看看,主要是打开浏览器,输入测试地址,并最大化窗口
seleniumUtil.launchBrowser(browserName, context,webUrl,timeOut);
} catch (Exception e) {
logger.error("浏览器不能正常工作,请检查是不是被手动关闭或者其他原因",e);
}
//设置一个testng上下文属性,将driver存起来,之后可以使用context随时取到,主要是提供arrow 获取driver对象使用的,因为arrow截图方法需要一个driver对象
testContext.setAttribute("SELENIUM_DRIVER", seleniumUtil.driver);
} @AfterClass
/**结束测试关闭浏览器*/
public void endTest() {
if (seleniumUtil.driver != null) {
//退出浏览器
seleniumUtil.quit();
} else {
logger.error("浏览器driver没有获得对象,退出操作失败");
Assert.fail("浏览器driver没有获得对象,退出操作失败");
}
}
}

在testng.xml添加如下代码:

    <!-- chrome,firefox,ghost和ie不区分大小写 -->
<parameter name="browserName" value="chrome" />
<!-- 页面元素10秒不出现超时时间 -->
<parameter name="timeOut" value="20" />
<!-- 定义测试的站点 -->
<parameter name="testurl" value="http://127.0.0.1:1080/WebTours/" />

5.7 页面类设计

FramePage.java:代码如下:

package com.demo.test.pages;

import org.openqa.selenium.By;

/**
* @description 这个类算不上一个page页,因为这个WebTours站点涉及到的frame比较多,所以我们把frame抓取出来用page页来存储
* */
public class FramePage { /**header frame名字 */
public static final By FP_FRAME_HEADER = By.name("header");
/** body frame 名字 */
public static final By FP_FRAME_BODY = By.name("body");
/**navbar frame名字*/
public static final By FP_FRAME_NAVBAR = By.name("navbar");
/**info frame名字*/
public static final By FP_FRAME_INFO = By.name("info");
}

HomePage.java:代码如下:

package com.demo.test.pages;

import org.openqa.selenium.By;

/**
* @description 首页面元素定位声明
* */
public class HomePage { /**用户名显示区域*/
public static final By HP_TEXT_USERNAME= By.xpath("//blockquote/b");
/**Flights按钮*/
public static final By HP_BUTTON_FLIGHTS = By.xpath("//*[@src='/WebTours/images/flights.gif']");
/**Itinerary按钮*/
public static final By HP_BUTTON_ITINERARY = By.xpath("//*[@src='/WebTours/images/itinerary.gif']");
/**Home按钮*/
public static final By HP_BUTTON_HOME = By.xpath("//*[@src='/WebTours/images/in_home.gif']");
/**Sign Off按钮*/
public static final By HP_BUTTON_SIGNOFF = By.xpath("//*[@src='/WebTours/images/signoff.gif']");
/**首页完整文本*/
public static final By HP_TEXT_HOME= By.xpath("//blockquote");
}

LoginPage.java:代码如下:

package com.demo.test.pages;

import org.openqa.selenium.By;
/**
* @description 登录页面元素定位声明
* */
public class LoginPage {
/**用户名输入框*/
public static final By LP_INPUT_USERNAME = By.name("username"); /**密码输入框*/
public static final By LP_INPUT_PASSWORD = By.name("password"); /**登录按钮*/
public static final By LP_BUTTON_LOGIN = By.name("login"); /**登录错误信息*/
public static final By LP_TEXT_ERROR= By.xpath("//*[@color='red']"); }

5.8 页面帮助类设计

FramePageHelper.java:代码如下:

package com.demo.test.pageshelper;

import org.openqa.selenium.By;

import com.demo.test.utils.SeleniumUtil;

/**
* @description 这个帮助类主要是进行frame的跳进和跳出的操作
* */
public class FramePageHelper { /** 进入frame-根据frame的元素定位进入 */
public static void jumpInToFrame(SeleniumUtil seleniumUtil, By by) {
seleniumUtil.switchFrame(seleniumUtil.findElementBy(by));
} /** 回到默认的frame */
public static void jumpOut(SeleniumUtil seleniumUtil) {
seleniumUtil.outFrame();
}
}

HomePageHelper.java:代码如下:

package com.demo.test.pageshelper;

import org.apache.log4j.Logger;

import com.demo.test.pages.FramePage;
import com.demo.test.pages.HomePage;
import com.demo.test.utils.SeleniumUtil; /**
* @desciption 首页帮助类:专门提供在这个页面进行操作的方法封装
*/
public class HomePageHelper {
// 提供本类中日志输出对象
public static Logger logger = Logger.getLogger(HomePageHelper.class); /**
* @author Young
* @description 等待首页元素加载
* @param seleniumUtil
* selenium api封装引用对象
* @param timeOut
* 等待元素超时的时间
* */
public static void waitHomePageLoad(SeleniumUtil seleniumUtil, int timeOut) {
FramePageHelper.jumpOut(seleniumUtil);
// 等待body frame显示出来
seleniumUtil.waitForElementToLoad(timeOut, FramePage.FP_FRAME_BODY);
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_BODY);// 先进入到body
// frame中
// 等待navbar frame显示出来
seleniumUtil.waitForElementToLoad(timeOut, FramePage.FP_FRAME_NAVBAR);
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_NAVBAR);// 再进入body
// frame的子frame:navbar
// frame中
logger.info("开始等待首页元素加载");
seleniumUtil.waitForElementToLoad(timeOut, HomePage.HP_BUTTON_FLIGHTS);
seleniumUtil
.waitForElementToLoad(timeOut, HomePage.HP_BUTTON_ITINERARY);
seleniumUtil.waitForElementToLoad(timeOut, HomePage.HP_BUTTON_HOME);
seleniumUtil.waitForElementToLoad(timeOut, HomePage.HP_BUTTON_SIGNOFF);
logger.info("首页元素加载完毕");
FramePageHelper.jumpOut(seleniumUtil); } /**
* @Description 登录成功之后验证用户名是不是正确的
* */
public static void checkUserName(SeleniumUtil seleniumUtil, int timeOut,
String username) {
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_BODY);// 先进入到body
// frame中
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_INFO);// 先进入到body
// frame中的子frame:info
// frame中
logger.info("开始检查用户名是不是:" + username);
seleniumUtil.isTextCorrect(
seleniumUtil.getText(HomePage.HP_TEXT_USERNAME), username);
logger.info("用户名检查完毕,用户名是:" + username); } /**
* @description 登录成功之后验证首页的文本内容
* */
public static void checkHomeText(SeleniumUtil seleniumUtil) {
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_BODY);// 先进入到body
// frame中
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_INFO);// 先进入到body
// frame中的子frame:info
// frame中
seleniumUtil
.isTextCorrect(
seleniumUtil.getText(HomePage.HP_TEXT_HOME),
"Welcome, jojo, to the Web Tours reservation pages."
+ "\n"
+ "Using the menu to the left, you can search for new flights to book, or review/edit the flights already booked. Don't forget to sign off when you're done!"); } }

LoginPageHelper.java:代码如下:

package com.demo.test.pageshelper;

import org.apache.log4j.Logger;

import com.demo.test.pages.FramePage;
import com.demo.test.pages.LoginPage;
import com.demo.test.utils.SeleniumUtil; /**
* @description 登录页面帮助类:提供在这个页面上做的操作的方法封装
* */
public class LoginPageHelper {
// 提供本类中日志输出对象
public static Logger logger = Logger.getLogger(LoginPageHelper.class); /**
* @description 等待登录页面元素加载
* @param seleniumUtil
* selenium api封装引用对象
* @param timeOut
* 等待元素超时的时间
* */
public static void waitLoginPageLoad(SeleniumUtil seleniumUtil, int timeOut) {
seleniumUtil.pause(1000);
// 对此处的解释:这个登录页面有两个大frame,一个header一个body ,
// 而登录的用户名、密码输入框以及登录按钮都在body frame下的navbar frame下,
// 所以先要进入body frame中,然后在进入navbar frame中,才能查找到登录界面的相关元素
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_BODY);// 先进入到body
// frame中
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_NAVBAR);// 再进入body
// frame的子frame:navbar
// frame中
logger.info("开始检查登录页面元素");
seleniumUtil.waitForElementToLoad(timeOut, LoginPage.LP_INPUT_USERNAME);
seleniumUtil.waitForElementToLoad(timeOut, LoginPage.LP_INPUT_PASSWORD);
seleniumUtil.waitForElementToLoad(timeOut, LoginPage.LP_BUTTON_LOGIN);
logger.info("检查登录页面元素完毕");
} /**
* @description 登录操作封装
* @param seleniumUtil
* selenium api封装引用对象
* @param username
* 用户名值
* @param password
* 用户密码值
* */
public static void typeLoginInfo(SeleniumUtil seleniumUtil,
String username, String password) { logger.info("开始输入登录信息");
// 清空用户名输入框
seleniumUtil.clear(LoginPage.LP_INPUT_USERNAME);
// 输入用户名到用户名输入框
seleniumUtil.type(LoginPage.LP_INPUT_USERNAME, username);
// 清空密码输入框
seleniumUtil.clear(LoginPage.LP_INPUT_PASSWORD);
// 输入密码到密码输入框
seleniumUtil.type(LoginPage.LP_INPUT_PASSWORD, password);
logger.info("输入登录信息完毕");
// 点击登录按钮
seleniumUtil.click(LoginPage.LP_BUTTON_LOGIN); } /**
* @description 验证登录错误信息
* @param seleniumUtil
* selenium api封装引用对象
* @param error
* 错误文本
* */
public static void checkLoginErrorInfo(SeleniumUtil seleniumUtil,
String error) {
FramePageHelper.jumpOut(seleniumUtil);
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_BODY);
FramePageHelper.jumpInToFrame(seleniumUtil, FramePage.FP_FRAME_INFO);
seleniumUtil.isTextCorrect(
seleniumUtil.getText(LoginPage.LP_TEXT_ERROR), error);
}
}

5.9 书写第一个用例

LoginPage_001_LoginSuccessFunction_Test:第一部分:LoginPage表明你是哪个模块的;第二部分:001,分用例编号,这个晚点会讲到用例编号的作用;第三部分:LoginSuccessFuntion这个是你用例具体要干嘛的一个简称;第四部分:Test每个用例以这个作为结尾后缀。每个部分之间必须以下划线”_”作为分隔符 。代码如下:

package com.demo.test.testcases.login;
import java.util.Map;
import org.testng.annotations.Test;
import com.demo.test.base.BaseParpare;
import com.demo.test.pageshelper.HomePageHelper;
import com.demo.test.pageshelper.LoginPageHelper;
/**
* @description 登录之后验证用户名是不是正确的
* */ public class LoginPage_001_LoginSuccessFunction_Test extends BaseParpare{
@Test
public void loginSuccessFunction() {
//等待登录页面加载
LoginPageHelper.waitLoginPageLoad(seleniumUtil, timeOut);
// 输入登录信息
LoginPageHelper.typeLoginInfo(seleniumUtil,"jojo", "bean");
//等待首页元素显示出来
HomePageHelper.waitHomePageLoad(seleniumUtil, timeOut);
//检查用户名是不是期望的"jojo"
HomePageHelper.checkUserName(seleniumUtil, timeOut, "jojo");
}
}

在testng.xml中添加代码如下:

    <!-- 定义测试模块,用test标签包围 -->
<test name="Login" preserve-order="true">
<packages>
<package name="com.demo.test.testcases.login" />
</packages>
</test>

5.10 完整的pom.xml和testng.xml

pom.xml代码如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>autotest</artifactId>
<version>0.0.1-SNAPSHOT</version> <dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>com.codeborne</groupId>
<artifactId>phantomjsdriver</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.qdox</groupId>
<artifactId>qdox</artifactId>
<version>1.12.1</version>
<scope>compile</scope>
</dependency> <dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-remote-driver</artifactId>
<version>LATEST</version>
</dependency>
</dependencies> <build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

testng.xml代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite">
<!-- driver的属性配置文件保存路径 -->
<parameter name="driverConfgFilePath" value="config/driver.properties" />
<!-- chrome,firefox,ghost和ie不区分大小写 -->
<parameter name="browserName" value="chrome" />
<!-- 页面元素10秒不出现超时时间 -->
<parameter name="timeOut" value="20" />
<!-- 定义测试的站点 -->
<parameter name="testurl" value="http://127.0.0.1:1080/WebTours/" /> <!-- 定义测试模块,用test标签包围 -->
<test name="Login" preserve-order="true">
<packages>
<package name="com.demo.test.testcases.login" />
</packages>
</test> <listeners>
<!-- arrow插件实现用例失败重跑以及生成测试报告 -->
<listener class-name="com.demo.test.plugins.arrow.TestResultListener" />
<listener class-name="com.demo.test.plugins.arrow.RetryListener" />
<listener class-name="com.demo.test.plugins.arrow.PowerEmailableReporter" />
</listeners>
</suite> <!-- Suite -->

6 配置测试报告目录


返回

Window->Preferences,然后再找到testng选项:Output directory,默认路径是/test-output 也就是项目根目录下会生成一个test-output的目录。修改为:/result/test-report

7 填加driver


返回

根据需要添加不同浏览器的driver。

8 执行用例


返回

右击testng.xml -> runas -> TestNG Suite