【框架】基于selenium+java框架设计(0-1实战)

发布于:2025-09-03 ⋅ 阅读:(12) ⋅ 点赞:(0)

概述:


接着上一篇继续谈论 Java 自动化框架的相关内容,本篇基于selenium,采用PO模式实践,包括技术栈、环境准备、框架结构、各模块功能、监听器、重试机制、依赖库、测试用例执行及报告生成等。关键要点包括:

  1. 技术栈:采用 Java + selenium3 + testng + jenkins + maven + ant + allure+Page Factory 技术栈。

  2. 环境准备:需准备 JDK1.8、Maven 环境、ant 环境、Jenkins 环境和 Allure 环境。

  3. BasePage 模块:封装了页面操作的基本方法,如输入字符、点击元素、清除文本等,还提供了元素定位、判断元素是否存在等功能。

  4. BrowserEngine 模块:负责初始化配置数据,根据配置选择浏览器并打开指定 URL,同时提供关闭浏览器、隐式等待等方法。

  5. 监听器:TestResultListener 监听器在测试用例失败时自动截图并保存到 allure 报告和本地,RetryListener 实现了最大 3 次的重试机制。

  6. 依赖库:项目依赖多个库,如 io.appium:java - client、org.seleniumhq.selenium:selenium - java 等,通过 Maven 进行管理。

  7. 测试执行与报告生成:使用 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、代码仓库地址:

java+selenium实现自动化框架vss_vss: 基于java+shelenium3+TestNG+Maven+allure+jenkins实现的java端UI自动化测试框架(selenium),采用PO模式进行封装,使得用例维护更加方便

16、Jenkins持续集成环境部署

  • 配置Jenkins自动化持续集成项目,即可实现远程服务器自动(构建,编译,打包)运行脚本,发送邮件测试报告等


网站公告

今日签到

点亮在社区的每一天
去签到