一、项目背景与技术选型
项目简介
目标系统:基于WebSocket的实时聊天室
核心功能:用户注册/登录、会话框发送信息、好友列表、信息发送
技术栈:html + Springboot + MySQL数据库
为什么选择Selenium
支持多浏览器兼容性测试(Chrome/Firefox/Edge)
完善的元素定位体系(XPath/CSS Selector)
动态内容处理能力(WebDriverWait机制)
跨平台执行能力(Windows/Linux/macOS)
测试环境
在开始测试之前,我们需要准备好测试环境。以下是测试所需的硬件和软件环境:
硬件:普通个人电脑
操作系统:Windows 10 或 Windows 11
浏览器:Chrome(版本 128.0.6613.84 或以上)
项目运行环境:maven、JDK1.8
自动化测试工具:Selenium + JUnit5
二、测试用例设计+手动测试
1. 用户注册
用户注册是聊天室的第一步,我们需要确保用户能够成功注册账号。以下是注册功能的测试用例:
测试场景1:输入正确的用户名和密码,点击注册按钮。
预期结果:注册成功,弹出提示框“注册成功!”。
测试场景2:输入已存在的用户名和密码,点击注册按钮。
预期结果:注册失败,弹出提示框“注册失败!”。
测试场景3:用户名或密码为空,点击注册按钮。
预期结果:注册失败,弹出提示框“您输入的用户名或密码为空!”。
2. 用户登录
用户登录是进入聊天室的关键步骤,以下是登录功能的测试用例:
测试场景1:输入正确的用户名和密码,点击登录按钮。
预期结果:弹出提示框“登录成功”,登录成功,跳转到会话页面。
测试场景2:输入正确的用户名,错误的密码,点击登录按钮。
预期结果:登录失败,弹出提示框“登录失败!”。
测试场景3:输入未注册的用户名和密码,点击登录按钮。
预期结果:登录失败,弹出提示框“登录失败!”。
测试场景4:用户名或密码为空,点击登录按钮。
预期结果:登录失败,弹出提示框“您输入的用户名或密码为空!”。
3. 会话功能
会话功能是聊天室的核心,以下是会话功能的测试用例:
测试场景1:未登录状态下,打开会话页面。
预期结果:弹出提示框“当前用户未登录!”。
测试场景2:登录状态下,点击会话按钮。
预期结果:会话按钮变绿,跳转会话列表页面。
测试场景3:点击具体会话,查看消息。
预期结果:消息页面显示当前会话的用户名和消息内容。
4. 消息发送和接收
消息发送和接收是聊天室的核心功能,以下是相关测试用例:
测试场景1:在输入框中输入消息,点击发送按钮。
预期结果:消息发送成功,输入框清空,消息页面显示发送的内容。
测试场景2:发送消息后,接收方查看消息。
预期结果:接收方能够看到发送方的消息。
等等场景
三、自动化测试实现
1.pom.xml文件引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
<artifactId>ChatroomAutoTest</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency><!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.9.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-suite -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
引入自动化测试工具:
selenium-java
用于编写和执行自动化测试脚本。webdrivermanager
用于自动管理 WebDriver 驱动程序,避免手动下载和配置。
引入文件操作工具:
commons-io
提供文件和 IO 操作的工具类,简化文件处理逻辑。
单元测试框架:通过 JUnit 5,可以编写和运行单元测试,验证网页聊天室的各项功能是否正常工作。
参数化测试:通过 JUnit Jupiter Params,可以为测试方法提供不同的参数,减少重复代码。
测试套件管理:通过 JUnit Platform Suite,可以将多个测试类或测试模块组合在一起,一次性运行。
2.写下AutoTestUtils类
方便在每个测试用例中来
- 获取浏览器驱动
- 获取屏幕截图,将用例执行结果保存下来
- 获取当前时间(记录每个屏幕截图是什么时候拍摄的)
package common;
import io.github.bonigarcia.wdm.WebDriverManager;
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.openqa.selenium.chrome.ChromeOptions;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Date;
public class Utils {
public static WebDriver driver;
public static WebDriver createDriver(){
if (driver == null){
WebDriverManager.chromedriver().setup();
ChromeOptions options = new ChromeOptions();
//允许访问所有链接
options.addArguments("--remote-allow-origins=*");
driver = new ChromeDriver(options);
//等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
}
return driver;
}
public Utils(String url){
//调用driver对象
driver = createDriver();
//访问url
driver.get(url);
}
public void getScreenShot(String str) throws IOException {
// ./src/test/image/
// /2025-02-15/
// /test01-174530.png
// /test02-174530.png
// /2025-02-16/
// /test01-174530.png
// /test02-174530.png
//屏幕截图
SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sim2 = new SimpleDateFormat("HHmmssSS");
String dirTime = sim1.format(System.currentTimeMillis());
String fileTime = sim2.format(System.currentTimeMillis());
//.src/test/image/2025-02-15/test-174530.png
String filename = "./src/test/image" + dirTime + "/" + str + "-" + fileTime + ".png";
System.out.println("filename:" + filename);
File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
//srcFile放到指定位置
FileUtils.copyFile(srcFile, new File(filename));
}
}
3.针对各种功能做自动化测试
3.1注册页面功能测试
RegisterPageTest.java
package Test;
import common.Utils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.IOException;
import java.time.Duration;
import static java.lang.Thread.sleep;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class RegisterPageTest extends Utils {
//获取到驱动
private static WebDriver driver = Utils.getDriver();
/**
* 打开网页
* 注册网页一般是从登录页面进入
*/
@BeforeAll
public static void openWeb() {
//打开登录页面
driver.get("http://127.0.0.1:8080/register.html");
//再点击登录页面的注册链接按钮
/*
driver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(4) > a")).click();
*/
driver.manage().window().maximize();
}
/**
* 验证网页正常打开
*/
@Test
@Order(1)
public void elementsAppear() throws IOException {
//用户名输入框
driver.findElement(By.cssSelector("#username"));
//密码输入框
driver.findElement(By.cssSelector("#password"));
//注册按钮
driver.findElement(By.cssSelector("#submit"));
//登录账号链接
/*
driver.findElement(By.cssSelector("body > div.register-container > div > div:nth-child(4) > a"));
*/
getScreenShot(getClass().getName());
}
/**
* 注册失败测试用例
* 参数: 尝试注册已注册用户
*/
@ParameterizedTest
@CsvSource({"zhangsan,123"})
@Order(2)
public void regFunTest(String username, String password) throws IOException {
// 输入测试数据
driver.findElement(By.cssSelector("#username")).sendKeys(username);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#submit")).click();
// 显式等待弹窗出现(最多等待10秒)
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
try {
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
String actual = alert.getText();
alert.accept(); // 关闭弹窗
Assertions.assertEquals("注册失败!", actual);
} catch (TimeoutException e) {
// 如果弹窗未出现,直接断言失败
Assertions.fail("未出现预期弹窗");
}
getScreenShot(getClass().getName());
}
@ParameterizedTest
@CsvSource({"zmjjk,123,注册成功!"})
@Order(3)
public void regTest(String username, String password, String expect) throws IOException {
// 输入测试数据
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#username")).sendKeys(username);
driver.findElement(By.cssSelector("#password")).sendKeys(password);
driver.findElement(By.cssSelector("#submit")).click();
// 显式等待弹窗
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
try {
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
String actual = alert.getText();
alert.accept(); // 关闭弹窗
Assertions.assertEquals(expect, actual);
} catch (TimeoutException e) {
Assertions.fail("未出现预期弹窗");
}
getScreenShot(getClass().getName());
}}
3.2登录页面功能测试
LoginPageTest.java
package Test;
import common.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import java.io.IOException;
import static java.lang.Thread.sleep;
public class LoginPageTest extends Utils {
//获取到驱动
private static WebDriver driver = Utils.getDriver();
/**
* 打开页面
* 注册页面一般是从登录页面进入
*/
@BeforeAll
public static void openWeb() {
//先打开登录页面
driver.get("http://127.0.0.1:8080/login.html");
driver.manage().window().maximize();
}
/**
* 检测登录页面是否能正常打开
*/
@Test
@Order(1)
public void elementsAppear() throws IOException {
//用户名输入框
driver.findElement(By.cssSelector("#username"));
//密码输入框
driver.findElement(By.cssSelector("#password"));
//注册账号链接
/*
driver.findElement(By.cssSelector("body > div.login-container > div > div:nth-child(4) > a"));
*/
//登录按钮
driver.findElement(By.cssSelector("#submit"));
//屏幕截图
getScreenShot(getClass().getName());
}
/**
* 异常测试登录功能
* 参数一:尝试登录未注册用户
* 参数二:密码输入错误,登录账号
*/
@ParameterizedTest
@CsvSource({"zhang,123","zhangsan,123456"})
@Order(2)
public void loginAbnormalTest(String username,String password) throws InterruptedException, IOException {
//拿到元素
WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
WebElement inputPassword = driver.findElement(By.cssSelector("#password"));
WebElement button = driver.findElement(By.cssSelector("#submit"));
//清除用户名,密码
inputUserName.clear();
inputPassword.clear();
//输入账号,密码
inputUserName.sendKeys(username);
inputPassword.sendKeys(password);
//点击登录
button.click();
//等待弹窗
sleep(3000);
//期望结果
String expect = "登录失败!";
String actual = "登录失败!";
Alert alert = driver.switchTo().alert();
if(alert != null) {
alert.accept();
} else {
actual = "当前用例执行失败!";
}
Assertions.assertEquals(expect,actual);
//屏幕截图
getScreenShot(getClass().getName());
}
/**
* 正常登录测试
*/
@ParameterizedTest
@CsvSource({"'zhangsan','123','登录成功!'"})
@Order(3)
public void loginNormalTest(String userName,String password,String expect) throws InterruptedException, IOException {
//发现元素
WebElement inputUserName = driver.findElement(By.cssSelector("#username"));
WebElement inputPassword = driver.findElement(By.cssSelector("#password"));
WebElement button = driver.findElement(By.cssSelector("#submit"));
//清除用户名密码
inputUserName.clear();
inputPassword.clear();
//输入用户名,密码
inputUserName.sendKeys(userName);
inputPassword.sendKeys(password);
//点击登录
button.click();
//等待弹窗
sleep(3000);
//期望结果
String actual = driver.switchTo().alert().getText();
//断言
Assertions.assertEquals(expect,actual);
//处理弹窗
driver.switchTo().alert().accept();
//屏幕截图
getScreenShot(getClass().getName());
}
}
3.3不登录页面功能测试
NoLoginTest.java
package Test;
import common.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.Alert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import java.io.IOException;
import static java.lang.Thread.sleep;
public class NoLoginTest extends Utils {
//获取驱动
private static WebDriver driver = Utils.getDriver();
@Test
public void Intercept() throws InterruptedException, IOException {
//在未登录的情况下,跳转会话页
driver.get("http://127.0.0.1:8080/client.html");
sleep(2000);
//期望结果
String expect = "当前用户未登录!";
String actual = "当前用户未登录!";
Alert alert = driver.switchTo().alert();
if (alert != null) {
alert.accept();
} else {
actual = "当前用例执行失败";
}
//屏幕截图
getScreenShot(getClass().getName());
//断言
Assertions.assertEquals(expect,actual);
}
}
3.4客户端页面功能测试
ClientPageTest.java
package Test;
import common.Utils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.IOException;
import java.time.Duration;
import static java.lang.Thread.sleep;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ClientPageTest extends Utils {
//获得驱动
public static WebDriver driver = Utils.getDriver();
/**
* 打开会话页面
*/
@BeforeAll
public static void openWeb() {
//先打开会话页面
driver.get("http://127.0.0.1:8080/client.html");
driver.manage().window().maximize();
}
/**
* 测试会话页面正常打开
*/
@Test
@Order(1)
public void elementsAppear() throws IOException {
//主页个人账号名
driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.user"));
//搜索输入框
driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.search > input[type=text]"));
//搜索按钮
driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.search > button"));
//会话标签图
driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.tab > div.tab-session"));
//好友标签图
driver.findElement(By.cssSelector("body > div.client-container > div > div.left > div.tab > div.tab-friend"));
//聊天输入框
driver.findElement(By.cssSelector("body > div.client-container > div > div.right > textarea"));
//发送按钮
driver.findElement(By.cssSelector("body > div.client-container > div > div.right > div.ctrl > button"));
//屏幕截图
getScreenShot(getClass().getName());
}
/**
* 点击具体会话
* 检验:消息页面展示当前会话用户名字
*/
@Test
@Order(2)
void SessionList() throws InterruptedException {
Thread.sleep(3000);
// 点击第一个会话项
driver.findElement(By.xpath("//*[@id=\"session-list\"]/li[1]/h3")).click();
Thread.sleep(3000);
// 显式等待标题元素加载并可见
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement titleElement = wait.until(ExpectedConditions.visibilityOfElementLocated(
By.cssSelector("body > div.client-container > div > div.right > div.title")
));
// 获取并处理文本
String actual = titleElement.getText().trim();
System.out.println("Actual title text: '" + actual + "'"); // 调试输出
// 断言
Assertions.assertEquals("lisi", actual);
}
/**
* 点击好友列表 (显式等待版本)
*/
@Order(3)
@Test
void FriendList() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 1. 点击好友标签
WebElement friendTab = wait.until(ExpectedConditions.elementToBeClickable(
By.cssSelector("div.tab-friend")
));
friendTab.click();
// 2. 等待好友列表加载
wait.until(ExpectedConditions.visibilityOfElementLocated(
By.id("friend-list")
));
// 3. 点击第一个好友
WebElement firstFriend = wait.until(ExpectedConditions.elementToBeClickable(
By.cssSelector("#friend-list li:first-child h4")
));
firstFriend.click();
// 4. 验证会话列表置顶项
WebElement selectedSession = wait.until(ExpectedConditions.visibilityOfElementLocated(
By.cssSelector("#session-list li.selected h3")
));
Assertions.assertEquals("lisi", selectedSession.getText());
}
/**
* 测试聊天发送功能 (显式等待版本)
*/
@ParameterizedTest
@CsvSource({"正在自动化测试中...", "测试发送成功", "测试结束!"})
@Order(4)
public void TalkSendTest(String msg) throws IOException {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 1. 点击第二个会话
WebElement secondSession = wait.until(ExpectedConditions.elementToBeClickable(
By.cssSelector("#session-list li:nth-child(2) h3")
));
secondSession.click();
// 2. 等待输入框就绪
WebElement inputBox = wait.until(ExpectedConditions.elementToBeClickable(
By.cssSelector("textarea")
));
// 3. 输入并发送消息
inputBox.sendKeys(msg);
driver.findElement(By.cssSelector("button")).click();
// 4. 验证消息发送成功(根据实际DOM结构调整选择器)
wait.until(ExpectedConditions.textToBePresentInElementLocated(
By.cssSelector(".message-show div:last-child p"),
msg
));
getScreenShot(getClass().getName());
}
/**
* 测试聊天接收功能 (显式等待版本)
*/
@Test
@Order(5)
public void TalkAcceptTest() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
// 1. 跳转登录页
driver.get("http://127.0.0.1:8080/login.html");
// 2. 填写登录表单
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("username")))
.sendKeys("lisi");
driver.findElement(By.id("password")).sendKeys("123");
driver.findElement(By.id("submit")).click();
// 3. 处理登录弹窗
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
alert.accept();
// 4. 等待主界面加载
wait.until(ExpectedConditions.titleContains("Client Page"));
// 5. 点击第一个会话
WebElement firstSession = wait.until(ExpectedConditions.elementToBeClickable(
By.cssSelector("#session-list li:first-child h3")
));
firstSession.click();
// 6. 验证会话对象
WebElement chatTitle = wait.until(ExpectedConditions.visibilityOfElementLocated(
By.cssSelector("div.title")
));
Assertions.assertEquals("zhangsan", chatTitle.getText());
// 7. 获取最新消息(动态定位最后一条)
WebElement lastMessage = wait.until(ExpectedConditions.presenceOfElementLocated(
By.cssSelector(".message-show div:last-child p")
));
Assertions.assertEquals("测试结束!", lastMessage.getText());
}
}
3.5退出驱动测试
DriverQuitTest.java
package common;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class DriverQuitTest extends Utils {
private static WebDriver driver = Utils.getDriver();
/**
* 退出驱动
*/
@Test
public void quitWeb() {
driver.quit();
}
}
3.6Junit套件使用
package Test;
import Test.ClientPageTest;
import Test.LoginPageTest;
import Test.NoLoginTest;
import Test.RegisterPageTest;
import common.DriverQuitTest;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
@Suite
@SelectClasses({RegisterPageTest.class, NoLoginTest.class, LoginPageTest.class, ClientPageTest.class, DriverQuitTest.class})
public class RunSuite {
public RunSuite() {
}
}
整体包、类、方法设计如下
有部分问题待解决,上述会话框问题和接收消息问题代码经过修改已经解决