本次框架使用Maven作为代码构建管理,引用了PO模式,将整体的代码分成了页面层、用例层、业务逻辑层。
框架搭建流程:
1、在pom.xml中引入依赖:
<!-- https://mvnrepository.com/artifact/io.appium/java-client -->
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>7.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.0.0</version>
<scope>test</scope>
</dependency>
<!--日志组件依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、在resources目录下添加好自己浏览器对应版本的chromedriver.exe,geckodriver.exe,IEDriverServer.exe;再添加一个log4j.properties文件,用于配置日志的打印格式信息,添加如下信息:
#根logger主要定义log4j支持的日志级别及输出目的地
log4j.rootLogger = DEBUG,console,file
###输出信息到控制台配置###
#表示输出到控制台
log4j.appender.console = org.apache.log4j.ConsoleAppender
#将System.out作为输出
log4j.appender.console.Target = System.out
#使用灵活的布局展示日志信息
log4j.appender.console.layout = org.apache.log4j.PatternLayout
#日志详细输出信息样式
log4j.appender.console.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
###输出信息到文件中配置###
#每天产生一个日志文件
log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
#输出文件目的地
log4j.appender.file.File = log/web_auto.log
#新的日志信息是否追加到旧的日志文件末尾
log4j.appender.file.Append = true
#使用灵活的布局展示日志信息
log4j.appender.file.layout = org.apache.log4j.PatternLayout
#日志详细输出信息样式
log4j.appender.file.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
3、将页面层的共性操作提取到common包下的BasePage类,将用例层的共性操作提取到common包下的BaseTest类。
package com.lrc.common;
import org.apache.log4j.Logger;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
/**
* @param
* @author lrc
* @create 2021/12/13
* @return
* @description 封装页面层的公用方法
**/
public class BasePage {
private static Logger logger = Logger.getLogger(BasePage.class);
/**
* 显式等待元素可见二次封装
* @param driver 驱动对象
* @param by 元素定位信息
*/
public WebElement waitElementVisible(RemoteWebDriver driver, By by ){
WebElement webElement = null;
try {
//1、实例化WebDriverWait 超时时间10s
WebDriverWait webDriverWait = new WebDriverWait(driver,10);
//2、通过until方法等到某个条件满足时为止
webElement = webDriverWait.until(ExpectedConditions.visibilityOfElementLocated(by));
}catch (Exception e){
logger.error("定位元素【"+by+"】异常");
}
return webElement;
}
/**
* 显式等待元素可被点击二次封装
* @param driver 驱动对象
* @param by 元素定位信息
*/
public WebElement waitElementClickable(RemoteWebDriver driver, By by ){
WebElement webElement =null;
try {
//1、实例化WebDriverWait 超时时间10s
WebDriverWait webDriverWait = new WebDriverWait(driver, 10);
//2、通过until方法等到某个条件满足时为止
webElement = webDriverWait.until(ExpectedConditions.elementToBeClickable(by));
}catch (Exception e){
logger.error("定位元素【"+by+"】异常");
}
return webElement;
}
/**
* 输入框输入数据通用方法
* @param driver 驱动对象
* @param by 元素定位信息
* @param data 输入的数据
*/
public void sendKey(RemoteWebDriver driver, By by,String data,String elementName){
logger.info("往元素【"+elementName+"】输入数据【"+data+"】");
waitElementVisible(driver,by).sendKeys(data);
}
/**
* 点击操作的通用方法
* @param driver 驱动对象
* @param by 元素定位信息
*/
public void click(RemoteWebDriver driver, By by,String elementName){
logger.info("对元素【"+elementName+"】进行点击");
waitElementClickable(driver,by).click();
}
/**
* 获取元素文本方法封装
* @param driver 驱动对象
* @param by 元素定位信息
* @param elementName 元素名称
* @return
*/
public String getText(RemoteWebDriver driver,By by,String elementName){
String text=waitElementVisible(driver,by).getText();
logger.info("获取元素【"+elementName+"】文本【"+text+"】");
return text;
}
/**
* 切换到指定IFrame封装
* @param driver 驱动对象
* @param by 元素定位信息
* @param frameInfo 自定义frame信息
*/
public void switchFrame(RemoteWebDriver driver,By by,String frameInfo){
WebElement element = waitElementVisible(driver, by);
logger.info("切换IFrame:"+frameInfo);
driver.switchTo().frame(element);
}
/**
* 从IFrame中切换到默认页面封装
* @param driver 驱动对象
*/
public void switchDefaultFrame(RemoteWebDriver driver){
logger.info("切换回默认的页面");
driver.switchTo().defaultContent();
}
/**
* Alert弹窗切换
* @param driver 驱动对象
*/
public void switchAlert(RemoteWebDriver driver){
logger.info("切换到alert窗口");
Alert alert = driver.switchTo().alert();
// alert.accept(); //点击确定
//alert.dismiss(); //点击取消
alert.getText(); //获取弹窗文本
}
}
package com.lrc.common;
import org.apache.log4j.Logger;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testng.Assert;
import java.util.Set;
/**
* @param
* @author lrc
* @create 2021/12/13
* @return
* @description 封装用例层的公用方法
**/
public class BaseTest {
private static Logger logger = Logger.getLogger(BaseTest.class);
public RemoteWebDriver driver;
/**
* 打开所有浏览器通用方法封装
*
* @param browserName 浏览器名
*/
public void openBrowser(String browserName) {
RemoteWebDriver webDriver = null;
if ("chrome".equalsIgnoreCase(browserName)) {
System.setProperty("webdriver.chrome.driver", "src\\test\\resources\\chromedriver.exe");
webDriver = new ChromeDriver();
logger.info("====================打开了chrome浏览器=====================");
} else if ("firefox".equalsIgnoreCase(browserName)) {
System.setProperty("webdriver.gecko.driver", "src\\test\\resources\\geckodriver.exe");
webDriver = new FirefoxDriver();
logger.info("====================打开了Firefox浏览器=====================");
} else if ("ie".equalsIgnoreCase(browserName)) {
DesiredCapabilities capabilities = new DesiredCapabilities();
//取消IE安全设置(忽略IE的Protected Mode的设置)
capabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true);
//忽略浏览器缩放设置
capabilities.setCapability(InternetExplorerDriver.IGNORE_ZOOM_SETTING, true);
System.setProperty("webdriver.ie.driver", "src\\test\\resources\\IEDriverServer.exe");
webDriver = new InternetExplorerDriver(capabilities);
logger.info("====================打开了IE浏览器=====================");
}
driver= webDriver;
}
/**
* 关闭浏览器通用方法
*/
public void closeBrowser(){
logger.info("====================关闭浏览器=====================");
driver.close();
}
/**
* 退出浏览器通用方法
*/
public void quitBrowser(){
logger.info("====================退出浏览器=====================");
driver.quit();
}
/**
* 最大化浏览器
*/
public void maxBrowser(){
logger.info("================最大化浏览器===================");
driver.manage().window().maximize();
}
/**
* 访问指定网址
* @param url 访问地址
*/
public void toURL(String url){
logger.info("================访问网址:==================="+url);
driver.get(url);
}
/**
* 封装的通用切换窗口的方法-根据对应窗口的标题来切换
* @param title 窗口标题
*/
public void switchWindowWithTitle(String title){
Set<String> allWindowHandles = driver.getWindowHandles();
for (String windowHandle: allWindowHandles){
//根据窗口的标题来进行判断
if(title.equals(driver.getTitle())){
break;
}else {
logger.info("切换到标题为:【"+title+"】的窗口");
driver.switchTo().window(windowHandle);
}
}
}
/**
* 封装的通用切换窗口的方法-根据对应窗口的url来切换
* @param url 窗口url
*/
public void switchWindowWithURL(String url){
Set<String> allWindowHandles = driver.getWindowHandles();
for (String windowHandle: allWindowHandles){
//根据窗口的URL来进行判断
if (url.equals(driver.getCurrentUrl())){
break;
}else {
logger.info("切换到url为:【"+url+"】的窗口");
driver.switchTo().window(windowHandle);
}
}
}
public void myAssertTrue(boolean condition,String assertDescription){
logger.info("断言:【"+assertDescription+"】条件表达式【"+condition+"】");
Assert.assertTrue(condition);
}
public void myAssertEquals(String actual,String expected,String assertDescription){
logger.info("断言:【"+assertDescription+"】实际值【"+actual+"】期望值【"+expected+"】");
Assert.assertEquals(actual,expected);
}
public void myAssertEquals(int actual,int expected,String assertDescription){
logger.info("断言:【"+assertDescription+"】实际值【"+actual+"】期望值【"+expected+"】");
Assert.assertEquals(actual,expected);
}
public void myAssertEquals(double actual,double expected,String assertDescription){
logger.info("断言:【"+assertDescription+"】实际值【"+actual+"】期望值【"+expected+"】");
Assert.assertEquals(actual,expected);
}
public void myAssertEquals(float actual,float expected,String assertDescription){
logger.info("断言:【"+assertDescription+"】实际值【"+actual+"】期望值【"+expected+"】");
Assert.assertEquals(actual,expected);
}
public void myAssertEquals(Object actual,Object expected,String assertDescription){
logger.info("断言:【"+assertDescription+"】实际值【"+actual+"】期望值【"+expected+"】");
Assert.assertEquals(actual,expected);
}
}
4、新建一个config包,新建一个全局配置数据类GlobalDatas,用于统筹管理项目的基础信息。
package com.lrc.config;
/**
* @param
* @author lrc
* @create 2021/12/13
* @return
* @description
**/
public class GlobalDatas {
//配置的浏览器
public static final String BROWSER_NAME="chrome";
//测试系统的登录账号
public static final String USER_NAME="筱筱创";
//测试系统的登录密码
public static final String USER_PASSWORD="123456";
//项目的URL地址
public static final String INDEX_URL="https://www.baidu.com";
//万能验证码
public static final String OMNIPOTENT_CODE="XXXX";
}
5、在page包下写一个百度页面的元素定位信息与操作方法:
package com.lrc.page;
import com.lrc.common.BasePage;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.RemoteWebDriver;
/**
* @param
* @author lrc
* @create 2021/12/22
* @return
* @description
**/
public class BaiduPage extends BasePage {
private RemoteWebDriver driver;
//搜索输入框
private By searchInputBy=By.id("kw");
//百度一下按钮
private By searchSubmitBy=By.id("su");
//新闻链接
private By newsLinkBy=By.xpath("//a[text()='新闻']");
//hao123链接
private By hao123LinkBy=By.xpath("//a[text()='hao123']");
//地图链接
private By mapLinkBy=By.xpath("//a[text()='地图']");
//贴吧链接
private By tieBaLinkBy=By.xpath("//a[text()='贴吧']");
//视频链接
private By videoLinkBy=By.xpath("//div[@id='s-top-left']/a[text()='视频']");
//图片链接
private By pictureLinkBy=By.xpath("//div[@id='s-top-left']/a[text()=' 图片']");
//网盘链接
private By panLinkBy=By.xpath("//div[@id='s-top-left']/a[text()='网盘']");
//更多链接
private By moreLinkBy=By.xpath("//div[@class='mnav s-top-more-btn']/a[text()='更多']");
//生成百度页面的构造方法
public BaiduPage(RemoteWebDriver driver) {
this.driver = driver;
}
//在页面层封装向百度搜索框输入数据的方法
public void inputData(String data){
sendKey(driver,searchInputBy, data,"百度搜索框");
}
//在页面层封装点击【百度一下】的方法
public void clickBaidu(){
click(driver,searchSubmitBy,"百度一下按钮");
}
//在页面层封装点击"新闻"的方法
public void clickNews(){
click(driver,newsLinkBy,"新闻链接");
}
//在页面层封装点击"hao123"的方法
public void clickHao123(){
click(driver,hao123LinkBy,"hao123链接");
}
//在页面层封装点击"地图"的方法
public void clickMap(){
click(driver,mapLinkBy,"地图链接");
}
//在页面层封装点击"贴吧"的方法
public void clickTieBa(){
click(driver,tieBaLinkBy,"贴吧链接");
}
//在页面层封装点击"视频"的方法
public void clickVideo(){
click(driver,videoLinkBy,"视频链接");
}
//在页面层封装点击"图片"的方法
public void clickPicture(){
click(driver,pictureLinkBy,"图片链接");
}
//在页面层封装点击"网盘"的方法
public void clickPan(){
click(driver,panLinkBy,"网盘链接");
}
}
6、在testcases用例层中编写百度页面的测试用例
package com.lrc.testcases;
import com.lrc.common.BaseTest;
import com.lrc.config.GlobalDatas;
import com.lrc.page.BaiduPage;
import org.testng.annotations.*;
/**
* @param
* @author lrc
* @create 2021/12/22
* @return
* @description
**/
public class TestBaidu extends BaseTest {
@BeforeMethod
public void setup(){
//用例前置
//1、打开浏览器
openBrowser(GlobalDatas.BROWSER_NAME);
maxBrowser();
//2、进入登录页面
toURL(GlobalDatas.INDEX_URL);
}
//测试百度搜索功能
@Test
public void test_baidu_success(){
BaiduPage baiduPage=new BaiduPage(driver);
baiduPage.inputData(GlobalDatas.USER_NAME);
baiduPage.clickBaidu();
}
//测试点击【新闻链接】
@Test
public void test_click_new(){
BaiduPage baiduPage=new BaiduPage(driver);
baiduPage.clickNews();
}
//测试点击【hao123】
@Test
public void test_click_hao123(){
BaiduPage baiduPage=new BaiduPage(driver);
baiduPage.clickHao123();
}
//测试点击【地图】
@Test
public void test_click_map(){
BaiduPage baiduPage=new BaiduPage(driver);
baiduPage.clickMap();
}
//测试点击【贴吧】
@Test
public void test_click_tieBa(){
BaiduPage baiduPage=new BaiduPage(driver);
baiduPage.clickTieBa();
}
//测试点击【视频】
@Test
public void test_click_video(){
BaiduPage baiduPage=new BaiduPage(driver);
baiduPage.clickVideo();
}
//测试点击【图片】
@Test
public void test_click_picture(){
BaiduPage baiduPage=new BaiduPage(driver);
baiduPage.clickPicture();
}
//测试点击【网盘】
@Test
public void test_click_pan(){
BaiduPage baiduPage=new BaiduPage(driver);
baiduPage.clickPan();
}
@AfterMethod
public void teardown(){
//用例后置
//退出浏览器
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
quitBrowser();
}
}
至此,测试框架的基本雏形已经搭建好了,在TestBaidu测试类右击执行即可运行测试用例了。
后续工作:
(1)实际复杂业务可能会涉及到很多个页面,比如:商城下单业务,每个用例执行的时候都需要先进行登录,选择商品等操作,此处只是设计了页面层、用例层,维护起来就显得很乏力,因为这里每个用例都需要执行登录,选择商品等操作,非常繁琐,为了解决这个问题,就需要添加个业务逻辑层,将很多用例都必须要执行到的共性操作封装到这个层,在用例层只需要调用业务逻辑层的方法即可完成复杂业务,使得维护起来方便许多
(2)使用Testng提供的DataProvider实现数据驱动,使得测试数据与用例能够解耦
(3)引入Allure报表,统计用例执行情况
(4)引入失败用例截图与重试机制,提高代码稳定性
(5)引入并行测试机制,提高代码执行效率
(6)配置GitLab/Github/SVN代码仓库,配置Jenkins拉取仓库代码,配置Jenkins自动化构建执行,输出Allure报告,发送邮件/钉钉等