基于Java+Maven+Testng+Selenium+Log4j+Allure+Jenkins搭建一个WebUI自动化框架(1)搭建框架基本雏形

本次框架使用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、(1)在resources目录下添加好自己电脑浏览器对应版本的驱动chromedriver.exe,geckodriver.exe;

驱动下载地址:https://npm.taobao.org/mirrors

(2)在resources目录下再添加一个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.howentech.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 rebort* @create 2025/07/08* @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 超时时间10sWebDriverWait 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 超时时间10sWebDriverWait 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();  //获取弹窗文本}}

创建BaseTest 

package com.howentech.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 rebort* @create 2025/07/08* @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.howentech.config;/*** @param* @author rebort* @create 2025/07/08* @return* @description**/
public class GlobalDatas {//配置的浏览器public static final String BROWSER_NAME="chrome";//测试系统的登录账号public static final String USER_NAME="rebort";//测试系统的登录密码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.howentech.page;import com.howentech.common.BasePage;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.RemoteWebDriver;/*** @param* @author rebort* @create 2025/07/08* @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.howentech.testcases;import com.howentech.common.BaseTest;
import com.howentech.config.GlobalDatas;
import com.howentech.page.BaiduPage;
import org.testng.annotations.*;/*** @param* @author rebort* @create 2025/07/08* @return* @description**/
public class TestBaidu extends BaseTest {@BeforeMethodpublic void setup(){//用例前置//1、打开浏览器openBrowser(GlobalDatas.BROWSER_NAME);maxBrowser();//2、进入登录页面toURL(GlobalDatas.INDEX_URL);}//测试百度搜索功能@Testpublic void test_baidu_success(){BaiduPage baiduPage=new BaiduPage(driver);baiduPage.inputData(GlobalDatas.USER_NAME);baiduPage.clickBaidu();}//测试点击【新闻链接】@Testpublic void test_click_new(){BaiduPage baiduPage=new BaiduPage(driver);baiduPage.clickNews();}//测试点击【hao123】@Testpublic void test_click_hao123(){BaiduPage baiduPage=new BaiduPage(driver);baiduPage.clickHao123();}//测试点击【地图】@Testpublic void test_click_map(){BaiduPage baiduPage=new BaiduPage(driver);baiduPage.clickMap();}//测试点击【贴吧】@Testpublic void test_click_tieBa(){BaiduPage baiduPage=new BaiduPage(driver);baiduPage.clickTieBa();}//测试点击【视频】@Testpublic void test_click_video(){BaiduPage baiduPage=new BaiduPage(driver);baiduPage.clickVideo();}//测试点击【图片】@Testpublic void test_click_picture(){BaiduPage baiduPage=new BaiduPage(driver);baiduPage.clickPicture();}//测试点击【网盘】@Testpublic void test_click_pan(){BaiduPage baiduPage=new BaiduPage(driver);baiduPage.clickPan();}@AfterMethodpublic 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报告,发送邮件/钉钉等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/913648.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/913648.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零构建MCP服务器:FastMCP实战指南

引言&#xff1a;MCP协议与FastMCP框架 Model Context Protocol&#xff08;MCP&#xff09;是连接AI模型与外部服务的标准化协议&#xff0c;允许LLM&#xff08;如Claude、Gemini&#xff09;调用工具、访问数据。然而&#xff0c;直接实现MCP协议需要处理JSON-RPC、会话管理…

基于FPGA的智能小车设计(包含代码)/ 全栈FPGA智能小车:Verilog实现蓝牙/语音/多传感器融合的移动平台

首先先声明一下&#xff0c;本项目已经历多轮测试&#xff0c;可以放心根据我的设计进行二次开发和直接套用&#xff01;&#xff01;&#xff01; 代码有详细的注释&#xff0c;方便同学进行学习&#xff01;&#xff01; 制作不易&#xff0c;记得三连哦&#xff0c;给我动…

Object.defineProperties 详解

Object.defineProperties 详解 Object.defineProperties 是 JavaScript 中用于在一个对象上定义或修改多个属性的方法。它是 Object.defineProperty 的复数版本&#xff0c;允许你一次性定义多个属性。 基本语法 Object.defineProperties(obj, props)obj&#xff1a;要在其上定…

MyBatis-Plus:深入探索与最佳实践

MyBatis-Plus作为MyBatis的增强版&#xff0c;已经在Java开发中得到了广泛应用。它不仅继承了MyBatis的所有功能&#xff0c;还提供了许多强大的扩展功能&#xff0c;帮助开发者提升开发效率和代码质量。本文将深入探讨MyBatis-Plus的高级特性及其在实际项目中的最佳实践。一、…

劳斯莱斯数字孪生技术:重构航空发动机运维的绿色革命

在航空工业迈向智能化的浪潮中&#xff0c;劳斯莱斯以数字孪生技术为核心&#xff0c;构建了发动机全生命周期管理的创新范式。这项技术不仅重新定义了航空发动机的维护策略&#xff0c;更通过数据驱动的决策体系&#xff0c;实现了运营效率与生态效益的双重突破。本文将从技术…

NPM组件 querypilot 等窃取主机敏感信息

【高危】NPM组件 querypilot 等窃取主机敏感信息 漏洞描述 当用户安装受影响版本的 querypilot 等NPM组件包时会窃取用户的主机名、用户名、工作目录、IP地址等信息并发送到攻击者可控的服务器地址。 MPS编号MPS-2kgq-v17b处置建议强烈建议修复发现时间2025-07-05投毒仓库np…

创业商业融资计划书PPT模版

创业商业融资计划书PPT模版&#xff1a;https://pan.quark.cn/s/25a043e4339e

解决GitHub仓库推送子文件夹后打不开的问题

从你描述的情况来看&#xff0c;IELTS_AI_Assessment 很可能被识别为了 Git 子模块&#xff08;submodule&#xff09;&#xff0c;而不是普通文件夹&#xff0c;这会导致在 GitHub 上无法直接打开查看内容。以下是具体原因和解决办法&#xff1a;为什么文件夹无法打开&#xf…

Web后端开发-请求响应

文章目录概述请求Postman简单参数原始方式SpringBootRequestParam注解小结实体参数数组集合参数日期参数Json参数路径参数总结响应响应-案例概述 请求 Postman 简单参数 原始方式 // 1. 简单参数 // 原始方式RequestMapping("/simpleParam")public String …

Javascript基础内容回顾—变量提升、事件循环和闭包等内容

以下是前端面试中 JavaScript 基础易错问题的详解&#xff0c;结合常见考点和易混淆概念进行解析&#xff1a; ⚠️ 一、变量作用域与提升 var vs let/const ◦ 变量提升&#xff1a;var 声明的变量会提升到作用域顶部&#xff08;值为 undefined&#xff09;&#xff0c;而 …

UNIX程序设计基本概念和术语

unix体系结构从严格意义上说&#xff0c;可将操作系统定义为一种软件&#xff0c;它控制计算机硬件资源&#xff0c;提供程序运行环境。我们通常将这种软件称为内核&#xff08;kernel&#xff09;&#xff0c;因为它相对较小&#xff0c;而且位于环境的核心。内核的接口被称为…

【WEB】Polar靶场 16-20题 详细笔记

目录 十六.签到题 十七.签到 十八.session文件包含 PHP 伪协议&#xff08;PHP Stream Wrappers&#xff09; base64加解密获取源代码 Session文件包含 namenameShell 是什么&#xff1f; 十九.Dont touch me 二十.robots robots.txt 十六.签到题 把didino改成didiy…

数据结构*搜索树

什么是搜索树 搜索树是一种树形数据结构&#xff0c;用于高效地存储和检索数据。其核心特点是每个节点包含一个键&#xff08;Key&#xff09;&#xff0c;并遵循特定的排序规则。常见的搜索树有二叉搜索树、自平衡二叉树、多叉搜索树等。AVL树、红黑树、Splay树都属于自平衡二…

语音交互新纪元:Hugging Face LeRobot如何让机器人真正“懂你”

机器人之言&#xff1a;早在2024年&#xff0c;Hugging Face正式进军真实世界机器人应用领域&#xff0c;推出了开源机器人项目LeRobot。LeRobot不仅仅是一个模型库&#xff0c;它是一个完整的机器人学习平台&#xff0c;融合了模仿学习、强化学习、数据可视化以及仿真环境。其…

搭建个人博客系列--MySql

前期提要&#xff1a;搭建个人博客系列--docker-CSDN博客 目前已经拥有了docker所以只需要将MySql安装在docker上即可。 一、在docker安装mysql docker pull mysql 二、查询docker内的mysql镜像 三、启动msql docker run -d -p 33060:3306 -v /home/mysql/conf:/mysql/conf.d…

【Spring】Spring Boot + OAuth2 + JWT + Gateway的完整落地方案,包含认证流程设计

Spring Boot OAuth2 JWT Gateway的完整落地方案&#xff0c;包含认证流程设计网关在服务中的使用一、整体架构设计二、核心组件实现1. OAuth2认证服务器&#xff08;auth-service&#xff09;2. JWT自定义增强&#xff08;存储用户信息&#xff09;三、Gateway全局拦截&…

第一个小程序

一、前言随着移动互联网的发展&#xff0c;用户对“即用即走”的轻量级应用需求日益增长&#xff0c;而传统 App 在下载安装、更新维护等方面存在一定的门槛。小程序应运而生&#xff0c;它是一种无需下载即可使用的应用程序形态。本文将带你完成人生中第一个微信小程序的开发全…

【办公类-54-07】20250901 2025学年第一学期班级点名册模版(双休国定假涂成灰色、修改标题和页眉,批量导出PDF)

背景需求: 制作了校历单后,第二个要制作的就是点名册(灰色版) 【办公类-54-03】20240828班级点名册模版(双休国定假涂成灰色)2024学年第一学期_姓名周一到周五的点名册怎么画-CSDN博客文章浏览阅读2.1k次,点赞24次,收藏4次。【办公类-54-03】20240828班级点名册模版(…

iOS App首次启动请求异常调试:一次冷启动链路抓包与初始化流程修复

在一次 iOS App 大版本更新后&#xff0c;部分用户反馈首次打开 App 时会出现“无法连接服务器”的提示&#xff0c;需要重启 App 才能正常使用。而后续使用过程中接口调用都正常。服务器端并未记录请求到达&#xff0c;日志中只有 sporadic&#xff08;零星&#xff09;断连记…

【Linux网络篇】:网络中的其他重要协议或技术——DNS,ICMP协议,NAT技术等

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;Linux篇–CSDN博客 文章目录其他重要协议或技术1.DNS2.ICMP协议3.NAT技术4.代理服务器其他重…