⾃动化实施步骤

1.编写web测试⽤例

2.⾃动化测试脚本开发

1.工具类:AutotestUtils
package com.example.blogautomatedtest.common; 
// 1. 驱动管理依赖:自动下载匹配版本的ChromeDriver,无需手动配置
import io.github.bonigarcia.wdm.WebDriverManager;
// 2. 文件操作依赖:处理截图文件的复制(POM已引入commons-io)
import org.apache.commons.io.FileUtils;
// 3. Selenium核心依赖:截图功能、Chrome驱动、浏览器配置
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class AutotestUtils {
    // 静态driver对象:全局复用,避免每个测试方法重复创建驱动
    public static ChromeDriver driver;

    // 初始化驱动方法:封装驱动创建逻辑,对外提供统一入口
    public static ChromeDriver createDriver() {
        // 判空:保证驱动只创建一次(单例模式),避免多次new ChromeDriver导致浏览器多开
        if (driver == null) {
            // 自动下载并配置ChromeDriver
            WebDriverManager.chromedriver().setup();

            // ChromeOptions配置:解决新版Chrome的兼容性问题
            ChromeOptions options = new ChromeOptions();
            options.addArguments("--no-sandbox"); // 解决Linux系统权限问题
            options.addArguments("--disable-dev-shm-usage"); // 解决容器/低配机内存不足问题
            options.addArguments("--remote-allow-origins=*"); // 解决新版Chrome跨域访问报错
            driver = new ChromeDriver(options); // 创建驱动实例

            // 隐式等待:全局等待元素加载(最多等10秒),避免“元素还没加载就定位”导致的报错
            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
            // 窗口最大化:避免元素因窗口大小问题定位失败
            driver.manage().window().maximize();
        }
        return driver; // 返回驱动实例,供测试类使用
    }

    // 初始化驱动并打开指定URL,测试类只需调用该方法即可
    public static void initDriver(String url) {
        ChromeDriver driver = createDriver();
        driver.get(url);
    }

    // 时间工具方法:生成无特殊字符的时间串,用于截图文件/目录命名
    public List<String> getTime() {
        Date now = new Date(); 
        // 目录名格式:20251229
        SimpleDateFormat dirFormat = new SimpleDateFormat("yyyyMMdd");
        // 文件名格式:20251229123030
        SimpleDateFormat fileFormat = new SimpleDateFormat("yyyyMMddHHmmss");

        String dirname = dirFormat.format(now);
        String filename = fileFormat.format(now);

        List<String> list = new ArrayList<>();
        list.add(dirname);
        list.add(filename); 
        return list; 
    }

    // 截图方法:封装截图逻辑,测试类只需调用即可生成截图
    public void getScreenShot(String str) throws IOException {
        List<String> list = getTime(); // 获取时间串
        // 截图保存路径:放到target目录(Maven构建目录),避免污染源码目录
        String savePath = "./target/screenshots/" + list.get(0) + "/" + str + "-" + list.get(1) + ".png";
        File destFile = new File(savePath); // 目标文件对象

        // 递归创建目录:如果父目录不存在(如20251229),自动创建,避免“文件找不到路径”报错
        if (!destFile.getParentFile().exists()) {
            destFile.getParentFile().mkdirs();
        }

        // 将driver转为TakesScreenshot接口(ChromeDriver实现了该接口),生成截图文件
        File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        // 将临时截图文件复制到指定路径
        FileUtils.copyFile(srcFile, destFile);
        System.out.println("截图已保存至:" + savePath); // 打印路径,方便调试
    }

    // 驱动销毁方法:避免测试结束后驱动残留(浏览器进程不关闭),导致内存泄漏
    public static void quitDriver() {
        if (driver != null) { // 判空:避免空指针
            driver.quit(); // 关闭浏览器+销毁驱动
            driver = null; // 重置driver,下次调用可重新创建
        }
    }
}
工具类核心逻辑
核心步骤代码实现核心目的
1. 全局驱动复用public static ChromeDriver driver;所有测试类共享同一个 driver,避免重复创建浏览器进程(节省资源、提升速度)
2. 驱动初始化(单例)createDriver() + if (driver == null)保证驱动只创建一次,解决 “多次调用导致浏览器多开” 问题;内置 WebDriverManager 自动管理驱动版本,无需手动下载配置
3. 驱动 + 页面初始化initDriver(String url)封装 “创建驱动 + 打开指定 URL” 两步,测试类只需传 URL 即可,无需重复写驱动初始化逻辑
4. 时间工具方法getTime()生成无特殊字符的 “日期目录名 + 时间戳文件名”,解决 “路径含特殊字符报错”“文件名重复覆盖” 问题
5. 截图方法getScreenShot(String str)1. 拼接自定义保存路径;2. 递归创建目录(避免路径不存在报错);3. 调用 Selenium 原生截图生成临时文件;4. 复制临时文件到自定义路径(永久保存);5. 打印路径方便调试
6. 驱动销毁quitDriver()测试结束后关闭浏览器、销毁驱动,避免内存泄漏 / 浏览器进程残留
工具类设计的核心思想
  1. 复用性:所有通用逻辑(驱动、截图、时间、销毁)都封装在工具类,测试类只需关注 “业务验证”(比如登录是否成功、列表页是否加载),无需重复写通用代码;
  2. 稳定性:加了判空、隐式等待、递归创建目录、异常处理,避免 “小问题导致测试中断”;
  3. 可维护性:后续要改截图路径、驱动配置,只需改工具类,不用改所有测试类;
  4. 可追溯性:截图按 “日期目录 + 类名 + 时间戳” 命名,测试失败后能快速找到对应的截图,定位问题。
关于mkdir() 和 mkdirs()讲解
对比 mkdir() 和 mkdirs()(关键区别):
方法作用场景
mkdir()只创建最后一级目录,上层目录必须存在已知父目录已存在时用
mkdirs()递归创建所有不存在的上层目录不确定父目录是否存在时用

第一次运行时,./target/screenshots/20251229/ 整个路径中:

  • target/:Maven 自动创建,已存在;
  • screenshots/:不存在;
  • 20251229/:不存在。

执行 destFile.getParentFile().mkdirs() 时:

  • destFile.getParentFile() 拿到的是 ./target/screenshots/20251229/;
  • mkdirs() 会先检查 screenshots/ 是否存在 → 不存在就创建;
  • 再检查 20251229/ 是否存在 → 不存在就创建;
  • 相当于一次性创建了 screenshots/ 和 20251229/ 两层目录。
对应代码片段
代码片段核心作用
str + "-" + list.get(1)拼接文件名(- 是普通字符,不是目录分隔符)
mkdirs()递归创建所有不存在的父目录(包括 screenshots/ 和 20251229/)
getScreenshotAs(OutputType.FILE)生成临时截图文件(Selenium 底层实现)
FileUtils.copyFile(srcFile, destFile)把临时截图复制到自定义路径(永久保存)
2.测试类:BlogListTest
package com.example.blogautomatedtest;

import com.example.blogautomatedtest.common.AutotestUtils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.testng.Assert; // TestNG断言:比原生assert更友好,失败时会提示详细信息
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.io.IOException;

import static com.example.blogautomatedtest.common.AutotestUtils.driver;

public class BlogLoginTest {

   
    @BeforeMethod
    public void setUp() {
        AutotestUtils.initDriver("http://localhost:8080/blog/login");
    }

    // 场景1:验证登录页是否正常加载
    @Test
    void loginPageLoadRight() throws IOException {
        // 定位登录页的核心元素(用户名/密码输入框),能定位到说明页面加载正常
        driver.findElement(By.cssSelector("#username"));
        driver.findElement(By.cssSelector("#password"));
        // 截图保存证据
        new AutotestUtils().getScreenShot(getClass().getName());
    }

    // 场景2:验证正确账号密码能登录成功
    @Test
    void loginSuc() {
        try {
            // 步骤1:清空输入框(避免残留数据影响测试)
            driver.findElement(By.cssSelector("#username")).clear();
            driver.findElement(By.cssSelector("#password")).clear();

            // 步骤2:输入正确账号密码
            driver.findElement(By.cssSelector("#username")).sendKeys("lisi");
            driver.findElement(By.cssSelector("#password")).sendKeys("123456");

            // 步骤3:点击登录按钮
            driver.findElement(By.cssSelector("#submit")).click();

            // 核心验证:定位列表页的特征元素,能定位到说明登录成功并跳转
            driver.findElement(By.cssSelector("div.blog-list"));

            // 截图:保存“登录成功”的证据,后缀区分场景
            new AutotestUtils().getScreenShot(getClass().getName() + "_loginSuc");

            // 回退到登录页:方便后续测试方法执行(可选)
            driver.navigate().back();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 场景3:验证错误账号密码登录失败
    @Test
    void loginFail() throws IOException, InterruptedException {
        // 1. 清空输入框
        driver.findElement(By.cssSelector("#username")).clear();
        driver.findElement(By.cssSelector("#password")).clear();

        // 2. 输入错误账号密码(当前触发的是“用户不存在”提示)
        driver.findElement(By.cssSelector("#username")).sendKeys("admin");
        driver.findElement(By.cssSelector("#password")).sendKeys("1111");

        // 3. 点击登录按钮
        driver.findElement(By.cssSelector("#submit")).click();

        // 4. 等待原生alert弹窗加载
        Thread.sleep(2000);

        // 5. 处理原生alert并获取文本
        Alert alert = driver.switchTo().alert();
        String actual = alert.getText(); // 实际提示文本(日志显示为“用户不存在”)
        alert.accept(); // 关闭弹窗

        // 6. 断言验证(注意:预期文本需与实际一致,当前实际是“用户不存在”)
        String expect = "用户不存在";
        Assert.assertEquals(actual, expect, "登录失败提示语不匹配");

        // 7. 截图保存证据
        new AutotestUtils().getScreenShot(getClass().getName() + "_loginFail");

        // 8. 回退到登录页
        driver.navigate().back();
    }

  
    @AfterMethod
    public void tearDown() {
        AutotestUtils.quitDriver();
    }


}

1.判断弹窗是浏览器原生 alert 还是 前端自定义弹窗
方法一触发弹窗后看开发者工具(最准确)
  1. 打开页面,按F12打开开发者工具(Elements 标签);
  2. 触发弹窗(比如输入错误密码点击登录);
  3. 观察 Elements 面板:
    • 如果是原生 alert:Elements 面板不会新增任何元素(原生 alert 属于浏览器层级,不在 DOM 树中);
    • 如果是自定义弹窗:Elements 面板会新增弹窗对应的 DOM 元素(比如<div class="popup">密码错误</div>)。
方法二尝试用 Selenium 的 Alert 接口测试(最直接)
@Test
void loginFail() throws IOException, InterruptedException { // 新增InterruptedException(Thread.sleep需要)
    // 1. 清空输入框
    driver.findElement(By.cssSelector("#username")).clear();
    driver.findElement(By.cssSelector("#password")).clear();

    //2. 输入错误的账号密码
    driver.findElement(By.cssSelector("#username")).sendKeys("admin");
    driver.findElement(By.cssSelector("#password")).sendKeys("1111");

    //3. 点击登陆按钮
    driver.findElement(By.cssSelector("#submit")).click();

    // 【关键】先等弹窗显示(加2秒等待,避免弹窗还没出来就检测)
    Thread.sleep(2000);

    // 【核心】先测试弹窗类型(注释掉断言,优先看日志)
    String expect = "密码错误!"; //预期提示语
    String actual = ""; // 先初始化
    try {
        // 尝试切换到原生alert
        Alert alert = driver.switchTo().alert();
        actual = alert.getText(); // 获取原生弹窗文本
        System.out.println("是原生alert弹窗,文本:" + actual);
        alert.accept(); // 关闭弹窗
    } catch (NoAlertPresentException e) {
        // 走到这里说明是自定义弹窗
        System.out.println("是前端自定义弹窗(非原生alert)");
        // 自定义弹窗暂时先不定位,仅打印日志
    }

    // 【临时注释】先不做断言(等确定弹窗类型后再补)
    // Assert.assertEquals(actual,expect,"登录失败提示语不匹配");

    // 5. 截图:保存“登陆失败”等证据
    new AutotestUtils().getScreenShot(getClass().getName() + "_loginFail"); 

    // 6.回退到登录页
    driver.navigate().back();
}
第二步:执行测试,看日志判断弹窗类型
  1. 运行loginFail方法;
  2. 看控制台输出:
    • 如果显示是原生alert弹窗,文本:密码错误! → 是浏览器原生弹窗;
    • 如果显示是前端自定义弹窗(非原生alert) → 是前端自己写的弹窗。
第三步:根据弹窗类型完善最终代码
情况 1:日志显示 “原生 alert 弹窗”

补充完整断言逻辑:

@Test
void loginFail() throws IOException, InterruptedException {
    // 1-3步:清空、输入错误账号密码、点击登录 → 不变
    
    // 点击登录后等弹窗
    Thread.sleep(2000);

    // 处理原生alert
    String expect = "密码错误!";
    Alert alert = driver.switchTo().alert();
    String actual = alert.getText();
    alert.accept(); // 关闭弹窗

    // 断言
    Assert.assertEquals(actual, expect, "登录失败提示语不匹配");

    // 截图+回退 → 不变
    AutotestUtils.getScreenShot(getClass().getName() + "_loginFail");
    driver.navigate().back();
}
情况 2:日志显示 “自定义弹窗”

需要先定位弹窗文本元素(F12 找选择器),再补充代码:

@Test
void loginFail() throws IOException, InterruptedException {
    // 1-3步:清空、输入错误账号密码、点击登录 → 不变
    
    // 点击登录后等弹窗
    Thread.sleep(2000);

    // 处理自定义弹窗(替换成你找到的选择器)
    String expect = "密码错误!";
    // 比如找到弹窗文本的选择器是“.error-popup-text”
    String actual = driver.findElement(By.cssSelector(".error-popup-text")).getText();
    // (可选)点击弹窗的“确定”按钮关闭
    driver.findElement(By.cssSelector(".popup-confirm-btn")).click();

    // 断言
    Assert.assertEquals(actual, expect, "登录失败提示语不匹配");

    // 截图+回退 → 不变
    AutotestUtils.getScreenShot(getClass().getName() + "_loginFail");
    driver.navigate().back();
}
  1. 必须加Thread.sleep(2000):点击登录后弹窗不会立刻显示,等待 2 秒确保弹窗加载完成,否则检测会出错;
  2. 先测类型,再做断言:避免因弹窗类型判断错误导致代码直接报错;
  3. 自定义弹窗定位技巧:触发弹窗后,F12 开发者工具按Ctrl+F输入 “密码错误”,找到包含该文本的元素,复制其 CSS 选择器。

开启idea中的断⾔功能

3.测试类BlogEditTest

package com.example.blogautomatedtest;

import com.example.blogautomatedtest.common.AutotestUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.Test;

import java.io.IOException;
import java.time.Duration;

import static com.example.blogautomatedtest.common.AutotestUtils.driver;

public class BlogEditTest {
    @Test
    void listPageOpen() {
        try {
            // 1. 创建显式等待对象(全局复用,超时时间10秒)
            WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

            // 2. 显式等待:菜单栏写博客按钮可点击,再点击
            WebElement writeBlogBtn = wait.until(
                    ExpectedConditions.elementToBeClickable(By.cssSelector("body > div.nav > a:nth-child(5)"))
            );
            writeBlogBtn.click();

            // 先找标题输入框
            WebElement titleInput = wait.until(
                    ExpectedConditions.visibilityOfElementLocated(By.cssSelector("#title"))
            );
            titleInput.clear();
            titleInput.sendKeys("自动化测试标题");


            // 3. 显式等待:发布文章按钮可点击,再点击
            WebElement submitBtn = wait.until(
                    ExpectedConditions.elementToBeClickable(By.cssSelector("#submit"))
            );
            submitBtn.click();

            // 4. 发布后等待1秒(确保页面响应),再截图
            Thread.sleep(1000);
            new AutotestUtils().getScreenShot(getClass().getName());

        } catch (IOException | InterruptedException e) { // 捕获sleep的中断异常
            throw new RuntimeException("操作失败:" + e.getMessage(), e);
        }
    }
}
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐