概述:
接着上一篇继续谈论 Java 自动化框架的相关内容,本篇基于selenium,采用PO模式实践,包括技术栈、环境准备、框架结构、各模块功能、监听器、重试机制、依赖库、测试用例执行及报告生成等。关键要点包括:
技术栈:采用 Java + selenium3 + testng + jenkins + maven + ant + allure+Page Factory 技术栈。
环境准备:需准备 JDK1.8、Maven 环境、ant 环境、Jenkins 环境和 Allure 环境。
BasePage 模块:封装了页面操作的基本方法,如输入字符、点击元素、清除文本等,还提供了元素定位、判断元素是否存在等功能。
BrowserEngine 模块:负责初始化配置数据,根据配置选择浏览器并打开指定 URL,同时提供关闭浏览器、隐式等待等方法。
监听器:TestResultListener 监听器在测试用例失败时自动截图并保存到 allure 报告和本地,RetryListener 实现了最大 3 次的重试机制。
依赖库:项目依赖多个库,如 io.appium:java - client、org.seleniumhq.selenium:selenium - java 等,通过 Maven 进行管理。
测试执行与报告生成:使用 mvn clean test 执行测试用例,mvn io.qameta.allure:allure - maven:serve 生成 allure 测试报告。
1、技术栈
Java+selenium3+testNG+jenkins+maven+Ant+allure+Docker+Page Factory
2、环境准备:
点击下直接跳转到对应依赖项进行下载
3、框架目录结构说明:
4、逻辑执行层模块划分说明:
BasePage
package com.howentech.framework;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
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 java.util.List;
/**
* @author rebort
*/
public class BasePage {
public static WebDriver driver;
public static String pageTitle;
public static String pageUrl;
/**
* 构造方法
*/
protected BasePage(WebDriver driver) {
BasePage.driver = driver;
}
/**
* 在文本框内输入字符
*/
protected void type(WebElement element, String text) {
try {
if (element.isEnabled()) {
element.clear();
Logger.Output(LogType.LogTypeName.INFO,
"Clean the value if any in " + element.toString() + ".");
element.sendKeys(text);
Logger.Output(LogType.LogTypeName.INFO, "Type value is: "
+ text + ".");
}
} catch (Exception e) {
Logger.Output(LogType.LogTypeName.ERROR, e.getMessage() + ".");
}
}
/**
* 点击元素,这里指点击鼠标左键
*/
protected void click(WebElement element) {
try {
if (element.isEnabled()) {
element.click();
Logger.Output(LogType.LogTypeName.INFO,
"Element: " + element.toString() + " was clicked.");
}
} catch (Exception e) {
Logger.Output(LogType.LogTypeName.ERROR, e.getMessage() + ".");
}
}
/**
* 在文本输入框执行清除操作
*/
protected void clean(WebElement element) {
try {
if (element.isEnabled()) {
element.clear();
Logger.Output(LogType.LogTypeName.INFO,
"Element " + element.toString() + " was cleaned.");
}
} catch (Exception e) {
Logger.Output(LogType.LogTypeName.ERROR, e.getMessage() + ".");
}
}
/**
* 判断一个页面元素是否显示在当前页面
*/
protected void verifyElementIsPresent(WebElement element) {
try {
if (element.isDisplayed()) {
Logger.Output(LogType.LogTypeName.INFO, "This Element "
+ element.toString().trim() + " is present.");
}
} catch (Exception e) {
Logger.Output(LogType.LogTypeName.ERROR, e.getMessage() + ".");
}
}
/**
* 获取页面的标题
*/
protected String getCurrentPageTitle() {
pageTitle = driver.getTitle();
Logger.Output(LogType.LogTypeName.INFO, "Current page title is "
+ pageTitle);
return pageTitle;
}
/**
* 获取页面的url
*/
protected String getCurrentPageUrl() {
pageUrl = driver.getCurrentUrl();
Logger.Output(LogType.LogTypeName.INFO, "Current page title is "
+ pageUrl);
return pageUrl;
}
/**
* 查找到某一元素并且给予相应的值,一般用在对文本输入框元素的定位和输入相应值
*/
public void sendKey(By by,String value){
driver.findElement(by).sendKeys(value);
}
/**
* 查找到某元素并点击
*/
public void elementClick(By by){
driver.findElement(by).click();
}
/**
* 先定位到相应的选择框
* 再定位到详细的待选项
* 一般用在定位下拉选择框中的某一个元素
*/
public void selectElement(By by,String text) {
Select select = new Select(driver.findElement(by));
select.selectByVisibleText(text);
}
/**
* 判断某个元素是否存在
*/
public boolean isElementExist(By by) {
try
{
Boolean bool = driver.findElement(by).isDisplayed();
return bool;
}
catch(NoSuchElementException e)
{
return false;
}
}
/**
* 获得某元素的文本描述信息
*/
public String getWebText(By by) {
try {
return driver.findElement(by).getText();
}
catch (NoSuchElementException e){
return "Text does not exist!";
}
}
/**
* 在一个元素集合中通过遍历文本内容定位到相应的元素
*/
public void clickElementContainingText(By by,String text) {
List<WebElement> elementList = driver.findElements(by);
for(WebElement e:elementList)
{
if(e.getText().contains(text))
{
e.click();
break;
}
}
}
/**
* 在一个元素集合中通过遍历文本内容获取相应的url
*/
public String getLinkUrlContainingText(By by, String text) {
List<WebElement> subscribeButton = driver.findElements(by);
String url = null;
for (WebElement e:subscribeButton) {
if (e.getText().contains(text))
{
url = e.getAttribute("href");
break;
}
}
return url;
}
/**
* 某些元素由于属于某个iframe的需要先定义到frame然后再在此frame里边查找该元素
* 此方法适合定位文本链接型元素
*/
public void frameElementClick(By by, String text) {
WebElement element = driver.switchTo().frame(text).findElement(by);
element.click();
}
/**
* 某些元素由于属于某个iframe的需要先定义到frame然后再在此frame里边查找该元素
* 此方法适合定位文本输入框型元素
*/
public void frameElementSendKey(By by,String text) {
WebElement element = driver.switchTo().frame(text).findElement(by);
element.sendKeys(text);
}
// 刷新浏览器
public static void refresh() {
driver.navigate().refresh();
}
// 浏览器前进
public static void forward() {
driver.navigate().forward();
}
// 浏览器后退
public static void back() {
driver.navigate().back();
}
//findElement元素等待的封装
public static WebElement findElement(final By by) {
WebElement webelement = null;
try {
webelement = new WebDriverWait(driver, 20).until(new ExpectedCondition<WebElement>() {
public WebElement apply(WebDriver webDriver) {
return driver.findElement(by);
}
});
} catch (Exception e) {
System.out.println("元素:" + by + "查找超时!!!");
e.printStackTrace();
}
return webelement;
}
/**
* findElements元素等待的封装
*/
public static List<WebElement> findElements(final By by) {
List<WebElement> WebElement = null;
try {
WebElement = new WebDriverWait(driver, 20).until(new ExpectedCondition<List<WebElement>>() {
@NullableDecl
public List<WebElement> apply(WebDriver webDriver) {
return driver.findElements(by);
}
});
} catch (Exception e) {
System.out.println("元素:" + by + "查找超时!!!");
e.printStackTrace();
}
return WebElement;
}
}
5、BrowserEngine,浏览器初始化
package com.howentech.framework;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.ie.InternetExplorerDriver;
/**
* @author rebort
*/
public class BrowserEngine {
public String browserName;
public String serverURL;
public WebDriver driver;
public void initConfigData() throws IOException{
Properties p = new Properties();
// 加载配置文件
InputStream ips = new FileInputStream(".\\TestConfig\\config.properties");
p.load(ips);
Logger.Output(LogType.LogTypeName.INFO, "开始从属性文件中选择浏览器名称");
browserName=p.getProperty("browserName");
Logger.Output(LogType.LogTypeName.INFO, "您选择的测试浏览器类型为: "+ browserName);
serverURL = p.getProperty("URL");
Logger.Output(LogType.LogTypeName.INFO, "测试服务器URL为: "+ serverURL);
ips.close();
}
public WebDriver getBrowser(){
if(browserName.equalsIgnoreCase("Firefox")){
System.setProperty("webdriver.gecko.driver", ".\\Tools\\geckodriver.exe");
driver = createFireFoxDriver();
Logger.Output(LogType.LogTypeName.INFO, "Launching Firefox ...");
}else if(browserName.equalsIgnoreCase("Chrome")){
System.setProperty("webdriver.chrome.driver", ".\\Tools\\chromedriver.exe");
driver= new ChromeDriver();
Logger.Output(LogType.LogTypeName.INFO, "Launching Chrome ...");
}else if(browserName.equalsIgnoreCase("IE")){
System.setProperty("webdriver.ie.driver", ".\\Tools\\EdgeriverServer.exe");
driver= new InternetExplorerDriver();
Logger.Output(LogType.LogTypeName.INFO, "Launching Edge...");
}
driver.get(serverURL);
Logger.Output(LogType.LogTypeName.INFO, "Open URL: "+ serverURL);
driver.manage().window().maximize();
Logger.Output(LogType.LogTypeName.INFO, "Maximize browser...");
callWait(5);
return driver;
}
/**
* 关闭浏览器并退出方法
*/
public void tearDown() throws InterruptedException{
Logger.Output(LogType.LogTypeName.INFO, "Closing browser...");
Thread.sleep(3000);
driver.quit();
}
// 关闭相应的页面
public void closeBrowser() {
driver.close();
}
/**
* 隐式时间等待方法
*/
public void callWait(int time){
driver.manage().timeouts().implicitlyWait(time, TimeUnit.SECONDS);
Logger.Output(LogType.LogTypeName.INFO, "Wait for "+time+" seconds.");
}
/**
* createFireFox Driver
* @Param: null
* @return: WebDriver
*/
private WebDriver createFireFoxDriver() {
WebDriver driver = null;
FirefoxProfile firefoxProfile = new FirefoxProfile();
firefoxProfile.setPreference("prefs.converted-to-utf8", true);
//set download folder to default folder: TestDownload
firefoxProfile.setPreference("browser.download.folderList", 2);
firefoxProfile.setPreference("browser.download.dir", ".\\TestDownload");
try {
// 旧版本的写法,新版本用下边的这个会报错:The constructor FirefoxDriver(FirefoxProfile) is undefined
//driver = new FirefoxDriver(firefoxProfile);
FirefoxOptions firefoxOptions = new FirefoxOptions();
firefoxOptions.setProfile(firefoxProfile);
} catch (Exception e) {
Logger.Output(LogType.LogTypeName.ERROR, e.getMessage());
Logger.Output(LogType.LogTypeName.ERROR, "启动Firefox驱动程序失败");
}
return driver;
}
}
6、Logger,与上一篇一样,日志类
package com.howentech.framework;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author rebort
*/
public class Logger {
public static String OutputFileName = getDateTimeByFormat(new Date(), "yyyyMMdd_HHmmss");
private static OutputStreamWriter outputStreamWriter;
private static String logFileName;
public static boolean LogFlag = true;
public Logger() {
}
private static void WriteLog(String logEntry) {
try {
// 定义日志文件保存路径和日志文件名称
logFileName = ".\\Log" + "\\" + OutputFileName + ".log";
if (outputStreamWriter == null) {
File logFile = new File(logFileName);
if (!logFile.exists())
logFile.createNewFile();
//利用OutputStreamWriter往日志文件写内容,字符编码是unicode
outputStreamWriter = new OutputStreamWriter(new FileOutputStream(logFileName), "utf-8");
}
outputStreamWriter.write(logEntry, 0, logEntry.length());
outputStreamWriter.flush();
} catch (Exception e) {
System.out.println(LogType.LogTypeName.ERROR.toString() + ": Failed to write the file " + logFileName);
e.printStackTrace();
}
}
//获取当前系统时间,得到格式化时间字符串
private static String getDateTimeByFormat(Date date, String format) {
SimpleDateFormat df = new SimpleDateFormat(format);
return df.format(date);
}
public static void Output(LogType.LogTypeName logTypeName, String logMessage) {
Date date = new Date();
String logTime = getDateTimeByFormat(date, "yyyy-MM-dd HH:mm:ss.SSS");
String logEntry = logTime + " " + logTypeName.name() + ": " + logMessage + "\r\n";
System.out.print(logEntry);
// 定义一个开关,为True就输出日志,如果你不想输出,改成False
if (LogFlag)
WriteLog(logEntry);
}
}
7、LogType
8、采用枚举类型进行读取,与上一篇一致
package com.howentech.framework;
/**
* @author rebort
*/
public class LogType {
public LogType(){
}
public enum LogTypeName{
//
INFO,
//
ERROR,
//
WARNING,
//
DEBUG;
}
}
9、监听器-用例失败自动将截图放在allure中
package com.howentech.listener;
import com.howentech.framework.BrowserEngine;
import io.qameta.allure.Attachment;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
/**
* @param
* @author rebort
* @create
* @return
* @description
**/
public class TestResultListener implements IHookable {
//并行测试
private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
public static WebDriver getDriver() {
return driver.get();
}
public void initConfigData() {
driver.set(new ChromeDriver());
}
@Override
public void run(IHookCallBack callBack, ITestResult result) {
System.out.println("=== 开始执行监听器 ===");
callBack.runTestMethod(result);
if(result.getThrowable() != null) {
System.out.println("检测到测试失败,异常信息: " + result.getThrowable().getMessage());
try {
// 反射获取测试类中的driver字段
Field driverField = result.getInstance().getClass().getDeclaredField("driver");
driverField.setAccessible(true);
WebDriver driver = (WebDriver) driverField.get(result.getInstance());
if(driver != null) {
System.out.println("尝试截图...");
byte[] screenshot = ((TakesScreenshot)driver).getScreenshotAs(OutputType.BYTES);
System.out.println("截图数据大小: " + (screenshot != null ? screenshot.length + "字节" : "null"));
saveScreenshotToAllure(screenshot);
FileUtils.copyFile(
((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE),
new File("screenshot/rebort_vss_test_" + System.currentTimeMillis() + ".png")
);
}
} catch (Exception e) {
System.err.println("监听器处理异常: ");
e.printStackTrace();
}
}
System.out.println("=== 监听器执行结束 ===");
}
@Attachment(value = "screenshot",type = "image/png")
public byte[] saveScreenshotToAllure(byte[] data){
//使用@Attachment注解来实现的返回的字节数组的数据 作为附件添加到Allure报表中
return data;
}
/**
* 生成字节数组的截图数据
* @param driver
* @return
*/
public byte[] takeScreenshotAsByte(WebDriver driver){
byte[] data = ((TakesScreenshot)driver).getScreenshotAs(OutputType.BYTES);
return data;
}
/**
* 生成截图以普通文件的形式,并且保存到本地(重构生成截图的方法,处理异常)
* @param driver
* @param fileName
*/
public void takeScreenshot(WebDriver driver, String fileName) {
try {
File destDir = new File(System.getProperty("user.dir"), "screenshot");
System.out.println("截图保存目录: " + destDir.getAbsolutePath());
FileUtils.forceMkdir(destDir); // 确保目录存在
File srcFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
File destFile = new File(destDir, fileName + ".png");
System.out.println("正在保存截图到: " + destFile.getAbsolutePath());
FileUtils.copyFile(srcFile, destFile);
System.out.println("截图保存成功");
} catch (Exception e) {
System.err.println("截图保存失败: ");
e.printStackTrace();
}
}
}
10、例如失败后截图如下效果:
正常登录成功是不会截图的,只有用例执行失败之后才会进行截图
正常执行情况:
以登录页面失败的测试用例为例子:
package com.howentech.testSuite;
import com.howentech.framework.BrowserEngine;
import com.howentech.framework.LogType;
import com.howentech.framework.Logger;
import com.howentech.listener.TestResultListener;
import com.howentech.pageObject.LoginPage;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@Listeners({TestResultListener.class}) // 确保监听器被加载
public class TestLogin extends BrowserEngine {
public WebDriver driver;
@BeforeClass
public void setUp() throws IOException {
BrowserEngine browserEngine = new BrowserEngine();
browserEngine.initConfigData();
driver = browserEngine.getBrowser();
}
@Test
public void testLogin() {
if (driver != null) {
LoginPage loginPage = PageFactory.initElements(driver, LoginPage.class);
driver.get("http://47.113.104.130:9966");
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
Logger.Output(LogType.LogTypeName.INFO, "vss正在进行登录...");
loginPage.login("admin", "Qwertyuivss@123");
Logger.Output(LogType.LogTypeName.INFO, "Login successfully!");
Assert.assertTrue(driver.getTitle().equals("vss"));
}
}
@AfterClass
public void tearDown () {
if (driver != null) {
driver.quit();
}
}
}
// Below is partial code of D:/vss-Java/vss_java_autoui_test/src/test/java/com/howentech/testSuite/TestLogin.java:
11、报告中看到效果
http://172.16.50.115:42479/index.html#suites/fd3717d44c5112a50035e8312a4fd3ca/9117bef411bd3ba7/
12、重试机制
package com.howentech.listener;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
/**
* @param
* @author rebort
* @create 2025/07/08
* @return
* @description
**/
public class RetryListener implements IRetryAnalyzer {
//最大重试次数
private int maxRetryCount=3;
//当前的重试次数
private int currentRetryCount=0;
@Override
public boolean retry(ITestResult result) {
//限制重试的最大次数,否则会进入死循环
if(currentRetryCount < maxRetryCount) {
//如果当前的重试次数没有达到限制,就去执行重试机制
currentRetryCount++;
return true;
}else {
return false;
}
}
}
12、依赖库相关
<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.howentech</groupId>
<artifactId>vss_java_autoui_test</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>vss_java_autoui_test</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<aspectj.version>1.9.7</aspectj.version>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<dependencies>
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>7.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
<!--testng组件依赖-->
<!-- 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>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<!--allure报表依赖-->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>2.18.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- maven-surefire-plugin 配合testng执行测试用例的maven插件 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<!-- 测试失败后,是否忽略并继续测试 -->
<testFailureIgnore>true</testFailureIgnore>
<suiteXmlFiles>
<!-- testng配置文件名称 -->
<suiteXmlFile>testng.xml</suiteXmlFile>
</suiteXmlFiles>
<!--设置参数命令行 -->
<argLine>
<!-- UTF-8编码 -->
-Dfile.encoding=UTF-8
<!-- 配置拦截器 -->
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
<systemProperties>
<property>
<!-- 配置 allure 结果存储路径 -->
<name>allure.results.directory</name>
<value>${project.build.directory}/allure-results</value>
</property>
</systemProperties>
</configuration>
<dependencies>
<!-- aspectjweaver maven坐标 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
测试类中的监听器加载修饰
@Listeners({TestResultListener.class}) // 确保监听器被加载
登录获取cookies持久化
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-support</artifactId>
<version>3.141.59</version>
</dependency>
package com.howentech.utils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.openqa.selenium.Cookie;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Set;
public class CookieUtils {
private static final Gson gson = new Gson();
private static final String COOKIE_FILE = "cookies.json";
// 保存 Cookies 到文件
public static void saveCookies(Set<Cookie> cookies) {
try (FileWriter writer = new FileWriter(COOKIE_FILE)) {
gson.toJson(cookies, writer);
} catch (Exception e) {
e.printStackTrace();
}
}
// 从文件加载 Cookies
public static Set<Cookie> loadCookies() {
try (FileReader reader = new FileReader(COOKIE_FILE)) {
return gson.fromJson(reader, new TypeToken<Set<Cookie>>() {}.getType());
} catch (Exception e) {
return null;
}
}
}
初始化浏览器的包中增加保存cookies的相关方法;
保持持久化需要保持驱动进程的运行,如果直接退出了这时候就不行;
tasklist | findStr chromedriver(查看启用后的进程)
taskkill /im chromedriver.exe -f(终止进程)
// 新增的 cookies 管理方法
/**
* 保存 cookies 到文件(使用 Gson 自动序列化)
*/
public void saveCookies() {
try {
File dir = new File("TestConfig");
if (!dir.exists()) dir.mkdirs();
Set<Cookie> cookies = driver.manage().getCookies();
try (FileWriter writer = new FileWriter(COOKIES_FILE)) {
new Gson().toJson(cookies, writer);
Logger.Output(LogType.LogTypeName.INFO, "Cookies 保存成功");
} catch (IOException e) {
Logger.Output(LogType.LogTypeName.ERROR, "保存 cookies 失败: " + e.getMessage());
}
}catch (Exception e) {
Logger.Output(LogType.LogTypeName.ERROR, e.getMessage());
Logger.Output(LogType.LogTypeName.ERROR, "保存 cookies 失败");
}
}
/**
* 从文件加载 cookies(使用 Gson 自动反序列化)
*/
public void loadCookies() {
try (FileReader reader = new FileReader(COOKIES_FILE)) {
Set<Cookie> cookies = gson.fromJson(reader, COOKIE_SET_TYPE);
if (cookies != null) {
driver.manage().deleteAllCookies();
cookies.forEach(driver.manage()::addCookie);
Logger.Output(LogType.LogTypeName.INFO, "Cookies 加载成功");
}
} catch (IOException e) {
Logger.Output(LogType.LogTypeName.INFO, "未找到 cookies 文件或加载失败");
}
}
//重构版--->
/**
* 从文件加载 cookies(使用 Gson 自动反序列化)
*/
public boolean loadCookies() {
try {
// 1. 检查文件是否存在
File cookiesFile = new File(COOKIES_FILE);
if (!cookiesFile.exists()) {
Logger.Output(LogType.LogTypeName.INFO, "未找到 cookies 文件: " + cookiesFile.getAbsolutePath());
return false;
}
// 2. 读取并解析 cookies
Set<Cookie> cookies;
try (FileReader reader = new FileReader(cookiesFile)) {
cookies = gson.fromJson(reader, COOKIE_SET_TYPE);
}
if (cookies == null || cookies.isEmpty()) {
Logger.Output(LogType.LogTypeName.WARNING, "Cookies 文件为空");
return false;
}
Logger.Output(LogType.LogTypeName.INFO, "开始加载 cookies 文件: " + cookiesFile.getAbsolutePath());
// 3. 必须先访问域名才能设置 cookies
driver.get(serverURL);
driver.manage().deleteAllCookies();
// 4. 添加所有 cookies(跳过无效的)
int successCount = 0;
for (Cookie cookie : cookies) {
try {
driver.manage().addCookie(cookie);
successCount++;
} catch (Exception e) {
Logger.Output(LogType.LogTypeName.WARNING,
"添加 Cookie 失败: " + cookie.getName() + " - " + e.getMessage());
}
}
Logger.Output(LogType.LogTypeName.INFO,
String.format("成功加载 %d/%d 个 cookies", successCount, cookies.size()));
// 5. 刷新页面使 cookies 生效
driver.navigate().refresh();
if (driver.getCurrentUrl().equals(serverURL)) {
Logger.Output(LogType.LogTypeName.INFO, "Cookies 无效,停留在登录页面");
}
return false;
} catch (Exception e) {
Logger.Output(LogType.LogTypeName.ERROR,
"加载 cookies 失败: " + e.getClass().getSimpleName() + " - " + e.getMessage());
}
return false;
}
//重构版本2
// 新增的 cookies 管理方法
/**
* 保存 cookies 到文件(使用 Gson 自动序列化)
*/
public void saveCookies() {
try {
//确保创建文件
File dir = new File("TestConfig");
if (!dir.exists()) dir.mkdirs();
Set<Cookie> cookies = driver.manage().getCookies();
try (FileWriter writer = new FileWriter(COOKIES_FILE)) {
new Gson().toJson(cookies, writer);
Logger.Output(LogType.LogTypeName.INFO, "Cookies 保存成功");
} catch (IOException e) {
Logger.Output(LogType.LogTypeName.ERROR, "保存 cookies 失败: " + e.getMessage());
}
}catch (Exception e) {
Logger.Output(LogType.LogTypeName.ERROR, e.getMessage());
Logger.Output(LogType.LogTypeName.ERROR, "保存 cookies 失败");
}
}
/**
* 从文件加载 cookies(使用 Gson 自动反序列化)
*/
public boolean loadCookies(String targetUrl) {
try {
// 1. 检查文件是否存在
File cookiesFile = new File(COOKIES_FILE);
if (!cookiesFile.exists()) {
Logger.Output(LogType.LogTypeName.INFO, "未找到cookies文件: " + cookiesFile.getAbsolutePath());
return false;
}
// 2. 读取并解析cookies
Set<Cookie> cookies;
try (FileReader reader = new FileReader(cookiesFile)) {
cookies = gson.fromJson(reader, COOKIE_SET_TYPE);
}
if (cookies == null || cookies.isEmpty()) {
Logger.Output(LogType.LogTypeName.WARNING, "Cookies文件为空");
return false;
}
// 3. 记录即将加载的cookies
Logger.Output(LogType.LogTypeName.DEBUG, "待加载的Cookies:");
cookies.forEach(c -> Logger.Output(LogType.LogTypeName.DEBUG,
String.format("%s=%s (domain=%s)", c.getName(), c.getValue(), c.getDomain())));
// 4. 访问域名并清除旧cookies
driver.get(serverURL);
driver.manage().deleteAllCookies();
Logger.Output(LogType.LogTypeName.INFO, "清除旧Cookies成功");
// 5. 添加cookies(带异常处理)
int successCount = 0;
for (Cookie cookie : cookies) {
try {
// 修正domain(移除端口号)
String domain = cookie.getDomain();
if (domain != null && domain.contains(":")) {
domain = domain.split(":")[0];
Cookie newCookie = new Cookie(
cookie.getName(),
cookie.getValue(),
domain,
cookie.getPath(),
cookie.getExpiry(),
cookie.isSecure(),
cookie.isHttpOnly()
);
driver.manage().addCookie(newCookie);
Logger.Output(LogType.LogTypeName.DEBUG, "Added Cookie: " + newCookie.toString());
} else {
driver.manage().addCookie(cookie);
Logger.Output(LogType.LogTypeName.DEBUG, "Added Cookie: " + cookie.toString());
}
successCount++;
} catch (Exception e) {
Logger.Output(LogType.LogTypeName.WARNING,
"添加Cookie失败: " + cookie.getName() + " - " + e.getMessage());
}
}
Logger.Output(LogType.LogTypeName.INFO,
"成功加载 " + successCount + "/" + cookies.size() + " 个Cookies");
// 6. 验证cookies有效性
driver.navigate().refresh();
driver.navigate().to(targetUrl);
new WebDriverWait(driver, 10).until(ExpectedConditions.urlContains(targetUrl));
boolean domReady = (Boolean) ((JavascriptExecutor) driver).executeScript("return document.readyState === 'complete'");
if (domReady) {
return true;
}
// 主动跳转到目标页面
driver.get(targetUrl);
// 双重验证:URL+页面元素
boolean urlValid = driver.getCurrentUrl().contains(targetUrl);
boolean pageReady = new WebDriverWait(driver, 10)
.until(d -> d.findElement(By.cssSelector("body")).isDisplayed());
Logger.Output(LogType.LogTypeName.INFO, "Cookies验证成功,已自动登录");
return urlValid && pageReady;
} catch (Exception e) {
Logger.Output(LogType.LogTypeName.ERROR, "跳转到目标页失败: " + e.getMessage());
Logger.Output(LogType.LogTypeName.ERROR,
"加载Cookies失败: " + e.getClass().getSimpleName() + " - " + e.getMessage());
return false;
}
}
执行登录一次后获取登录成功后的cookies,将cookies保存在配置文件中
测试用例监听器
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" parallel="tests" thread-count="2">
<!--使监听器生效-->
<listeners>
<listener class-name="com.howentech.listener.TestResultListener"></listener>
<listener class-name="com.howentech.listener.GlobalAnnotationTransformer"></listener>
</listeners>
<test name="测试">
<classes>
<class name="com.howentech.testSuite.testBasePage"/>
</classes>
</test>
</suite>
13、执行测试用例
mvn clean test
14、生成allure测试报告
mvn io.qameta.allure:allure-maven:serve
用例中使用注解:
类级
// 确保监听器被加载
@Epic("vss登录功能") //登录模块大模块划分
@Feature("vss用户认证") // 模块下的二级功能-如登录页-登录
@Listeners({TestResultListener.class, AllureTestNg.class})
-----------------------------------------------
方法级
@Test
@Story("管理员登录") //登录用例表述
@Description("测试vss管理员登录系统") // 详情描述
@Owner("作者:Rebort") //作者信息
用例注解,描述用例标题
-----------------------------------------------
代码块级
Allure.step("打开vss登录页面"); //用例执行步骤
用例执行体中的步骤:
例子:登录页测试用例执行后生成的测试报告:
http://172.16.50.115:63572/index.html#behaviors/61eb2f3dbcfaeb44b5ec407cc872dd0f/98755ba210761f30/
点击对应模块进入后可以查看执行后的测试用例详情,以及看到添加注解后的测试用例描述信息
登录测试用例
package com.howentech.testSuite;
import com.howentech.framework.BrowserEngine;
import com.howentech.framework.LogType;
import com.howentech.framework.Logger;
import com.howentech.listener.TestResultListener;
import com.howentech.pageObject.LoginPage;
import io.qameta.allure.*;
import io.qameta.allure.testng.AllureTestNg;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import java.io.IOException;
// 确保监听器被加载
@Epic("vss登录功能")
@Feature("vss登录页-登录功能测试")
@Listeners({TestResultListener.class, AllureTestNg.class})
public class TestLogin extends BrowserEngine {
public WebDriver driver;
@BeforeClass
public void setUp() throws IOException {
BrowserEngine browserEngine = new BrowserEngine();
browserEngine.initConfigData();
driver = browserEngine.getBrowser();
}
@Test
@Story("管理员登录")
@Description("测试vss管理员用户登录系统")
@Owner("作者:Rebort")
public void testLogin() {
if (driver != null) {
Allure.step("打开vss登录页面");
LoginPage loginPage = PageFactory.initElements(driver, LoginPage.class);
driver.get("http://172.16.50.164:9966/");
Logger.Output(LogType.LogTypeName.INFO, "vss正在进行登录...");
Allure.step("执行登录输入");
loginPage.login("admin", "admin@A123");
Allure.step("用户名密码正确登录成功");
Assert.assertTrue(driver.getCurrentUrl().equals("http://172.16.50.164:9966/vss/"));
Logger.Output(LogType.LogTypeName.INFO, "Login successfully!");
}
}
@AfterClass
public void tearDown () {
if (driver != null) {
driver.quit();
}
}
}
// Below is partial code of D:/vss-Java/vss_java_autoui_test/src/test/java/com/howentech/testSuite/TestLogin.java:
15、代码仓库地址:
16、Jenkins持续集成环境部署
配置Jenkins自动化持续集成项目,即可实现远程服务器自动(构建,编译,打包)运行脚本,发送邮件测试报告等