目录
畅言空间自动化测试
一. 项目
对网页聊天室, 基于web项目进行完整的自动化测试
1. 项目名称
畅言空间
2. 测试时间
测试时间:
2025.4~2025.4
3. 项目背景
随着互联网技术的飞速发展,实时通讯和交流成为人们日常生活不可或缺的部分。即时通讯工具为用户提供了便捷 、高效的沟通方式 ,而聊天室作为一种经典的实时交互场景,能够满足多人同时在线交流、分享信息和互动娱乐的 需求。
利用 Java 语言面向对象、跨平台、安全性高等特性,构建稳定可靠的后端服务。配合 WebSocket 协议实现全双工通信,替代传统轮询机制,确保消息毫秒级推送。使用 Spring Boot 框架,通过依赖 注入、面向切面编程等,提高开发效率。HTML 作为网页内容的基础结构,用于定义页面的各种元素,如标题、文本、图片、表单等。选用 MySQL 关系型数据库存储用户信息、聊天记录等结构化数据,确保 数据的一致性和完整性,通过 JDBC 技术实现 Java 程序与数据库的交互。可以实现实时与好友聊天, 覆盖用户登录, 好友列表, 消息传输等功能。
二. 测试过程
1. 测试环境与配置
系统环境: Windows 11
浏览器: Edge
自动化测试工具: Java + Selenium
2. 编写web测试用例
3. 执行测试用例
3.1 登录页面测试
输入账号密码
输入正确账号密码, 弹出登陆成功弹窗
账号密码错误, 弹出登陆失败弹窗
3.2 注册页面展示
输入想要注册的账号和密码
点击注册, 注册成功
输入已注册的账号, 有注册失败弹窗
3.3 聊天页面展示
查看具体聊天内容
消息列表
可以点击需要聊天的会话, 和搜索栏填写文本, 点击搜索按钮
输入框输入内容吗发送可点击
4. 创建空项目
4.1 添加所需依赖
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.7</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.15.0</version>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.6.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>
Logback 是 SLF4J(Simple Logging Facade for Java)的原生实现,SLF4J 是一个日志门面,而 Logback 作为具体的日志实现框架,提供了强大的日志记录功能。
注入Selenium: 一个广泛使用的 Web 自动化测试工具, 支持多种浏览器, 可以在不同的操作系统上运行
WebDriverManager: 用于自动下载和管理浏览器驱动(如 ChromeDriver、GeckoDriver 等),避免了手动下载和配置驱动的繁琐过程。
4.2 创建公共目录方法
在test文件夹下创建 java文件夹, 在Java文件夹创建common, tests文件夹
在common公共文件夹下, 创建 Utils 方法
实现WebDriver实例的创建与截图功能
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.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
public class Utils {
protected static WebDriver driver;
public static WebDriver createDriver()
{
if(driver == null)
{
WebDriverManager.edgedriver().setup();
EdgeOptions options = new EdgeOptions();
//允许访问所有的链接
options.addArguments("--remote-allow-origins=*");
driver = new EdgeDriver(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 {
// 编辑路径
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("HHmmssSS");
String dirTime = simpleDateFormat.format(System.currentTimeMillis());
String fileTime = simpleDateFormat2.format(System.currentTimeMillis());
//./src/test/image/-2025-04-10/test-10342445.png
String filename = "./src/test/image/" + dirTime + "/" + str + "-" + fileTime + ".png";
File srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
// 创建保存截图的目录
File directory = new File("./src/test/image/" + dirTime);
// srcFile放到指定位置
FileUtils.copyFile(srcFile, new File(filename));
}
}
创建驱动对象
单例模式:createDriver方法采用单例模式,保证WebDriver实例仅被创建一次。要是driver为null,就会创建一个新的EdgeDriver实例;反之,则返回现有的实例。
所有测试的用例共用一个driver对象
错误日志截图功能
时间格式化:运用SimpleDateFormat类对当前时间进行格式化,生成日期和时间字符串,用于构建截图文件的目录和文件名。
截图保存:借助TakesScreenshot接口将当前浏览器页面截图保存为文件,再使用FileUtils.copyFile方法把截图文件复制到指定的目录。
目录创建:在保存截图前,会检查并创建保存截图的目录。
4.3 登录页面
在tests文件夹下, 创建LoginPage
实现检测登录成功和登录失败
package tests;
import common.Utils;
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;
public class LoginPage extends Utils {
public static String url = "http://123.57.148.179:8080/login.html";
public LoginPage() {
super(url);
}
/**
* 检查页面是否加载成功
*/
public void Login() throws IOException {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.nav")));
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.login-container")));
}
/**
* 检查登录功能 -> 登录成功
*/
public void LoginSuc() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
//避免因截图失败导致程序中断
try {
driver.findElement(By.cssSelector("#username")).sendKeys("zhangsan");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
// 等待登录成功的警告弹窗并处理
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
alert.accept();
// 等待发送按钮出现,确认登录成功
WebElement sendButton = wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.client-container > div > div.right > div.ctrl > button")));
getScreenShot(getClass().getName());
driver.navigate().back();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 检查登录功能 -> 登陆失败
*/
public void LoginFail() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
//避免因截图失败导致程序中断
try {
driver.navigate().refresh();
driver.findElement(By.cssSelector("#username")).sendKeys("liusanpao");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
// 等待登录失败警告弹窗出现并处理
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
alert.accept();
WebElement sendButton = wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.client-container > div > div.right > div.ctrl > button")));
// 获取发生报错时屏幕截图
getScreenShot(getClass().getName());
} catch (IOException e) {
e.printStackTrace();
}
}
}
方法通过继承 Utils 类提高了代码的复用性
代码中多次使用了 WebDriverWait 类进行显式等待。避免因页面加载缓慢而导致的元素找不到的问题。
通过 By.cssSelector 方法使用 CSS 选择器来定位页面元素。有通过 ID 定位用户名输入框并输入用户名, 检测元素是否存在等功能。
使用 Alert 类来处理警告弹窗。在登录成功或失败时,页面可能会弹出警告弹窗,使用 alert.accept() 接受弹窗。
4.4 注册页面
在tests文件夹下, 创建RegisterPage方法
package tests;
import common.Utils;
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;
public class RegisterPage extends Utils {
public static String url = "http://123.57.148.179:8080/register.html";
public static String loginUrl = "http://123.57.148.179:8080/login.html";
public RegisterPage() {
super(url);
}
/**
* 检查注册页面是否加载成功
*/
public void checkPageLoad() throws IOException {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
// 检查导航栏和注册容器是否加载
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.nav")));
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.register-container")));
// 检查关键元素是否存在
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("username")));
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("password")));
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("confirm-password")));
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("submit")));
getScreenShot(getClass().getName());
}
/**
* 测试注册成功场景
*/
public void registerSuccess() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
try {
// 生成随机用户名确保每次测试都是新用户
String randomUser = "testuser_" + System.currentTimeMillis();
// 填写注册表单
driver.findElement(By.id("username")).sendKeys(randomUser);
driver.findElement(By.id("password")).sendKeys("Test1234");
driver.findElement(By.id("confirm-password")).sendKeys("Test1234");
WebElement submitButton = driver.findElement(By.cssSelector("#submit"));
submitButton.click();
getScreenShot(getClass().getName());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 测试密码不匹配场景
*/
public void registerPasswordMismatch() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
try {
driver.navigate().refresh();
// 填写不匹配的密码
driver.findElement(By.id("username")).sendKeys("testuser");
driver.findElement(By.id("password")).sendKeys("Test1234");
driver.findElement(By.id("confirm-password")).sendKeys("Different123");
driver.findElement(By.id("submit")).click();
// 处理密码不匹配的alert
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
String alertText = alert.getText();
alert.accept();
// 验证页面未跳转
wait.until(ExpectedConditions.urlToBe(url));
getScreenShot(getClass().getName());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 测试必填字段为空场景
*/
public void registerEmptyFields() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
try {
driver.navigate().refresh();
// 不填写任何字段直接提交
driver.findElement(By.id("submit")).click();
// 处理字段为空的alert
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
String alertText = alert.getText();
alert.accept();
// 验证页面未跳转
wait.until(ExpectedConditions.urlToBe(url));
getScreenShot(getClass().getName());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 测试用户已存在场景
*/
public void registerUserExists() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
try {
driver.navigate().refresh();
// 使用已存在的用户注册
driver.findElement(By.id("username")).sendKeys("zhangsan");
driver.findElement(By.id("password")).sendKeys("123");
driver.findElement(By.id("confirm-password")).sendKeys("123");
driver.findElement(By.id("submit")).click();
// 处理用户已存在的alert
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
String alertText = alert.getText();
alert.accept();
// 验证页面未跳转
wait.until(ExpectedConditions.urlToBe(url));
getScreenShot(getClass().getName());
} catch (IOException e) {
e.printStackTrace();
}
}
}
通过 import 语句引入了其他必要的类和包,如 org.openqa.selenium 相关类用于自动化测试,java.io.IOException 用于处理输入输出异常,java.time.Duration 用于设置等待时间。
使用 System.currentTimeMillis() 生成随机用户名,确保每次测试时使用的都是新用户。
在各个测试方法中,使用 try-catch 块捕获 IOException 异常,并打印异常堆栈信息。这有助于在测试过程中出现异常时进行调试。
4.5 聊天页面
在tests文件夹下, 创建ClientPage方法
package tests;
import common.Utils;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import java.io.IOException;
import java.time.Duration;
public class ClientPage extends Utils {
public static String url = "http://123.57.148.179:8080/client.html";
public static String url2 = "http://123.57.148.179:8080/login.html";
private WebDriverWait wait;
public ClientPage() {
super(url);
// 初始化 WebDriverWait 对象,设置最长等待时间为 10 秒
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
// 未登录状态下访问联系人页面
public void ClientNoLogin() throws IOException {
// 打开指定页面
driver.get(url);
getScreenShot(getClass().getName());
// 无法找到发送按钮则处于未登录
WebElement sendButton = wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.client-container > div > div.right > div.ctrl > button")));
// 跳转到登录页面
String expect = driver.getTitle();
// 断言页面标题是否符合预期
assert expect.equals("网页聊天");
}
//检查页面元素
public void elementFind() 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 > button"));
//搜索按钮
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());
}
/**
* 点击联系人, 消息列表展示联系人名称
*/
public void clickFriend() throws IOException {
//点击联系人列表
WebElement conversationList = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='client-container']/div/div[@class='left']/div[@class='tab']/div[@class='tab-friend']")));
conversationList.click();
// 点击 wangwu 对话
WebElement Conversation = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//*[@id=\"friend-list\"]/li[2]/h4")));
Conversation.click();
// 消息页面展示用户名的元素选择器
WebElement usernameElement = wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//*[@id=\"session-list\"]/li[@class=\"selected\"]/h3")));
assert usernameElement.isDisplayed();
}
/**
* 测试聊天发送功能
* 参数:zhangsan 给 zhaoliu 发送数据
*/
public void testChatSending() throws IOException {
// 点击联系人列表
WebElement conversationList = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='client-container']/div/div[@class='left']/div[@class='tab']/div[@class='tab-friend']")));
conversationList.click();
// 点击与 zhaoliu 的对话
WebElement receiverConversation = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//*[@id='friend-list']/li[3]/h4")));
receiverConversation.click();
// 找到聊天输入框并输入消息
WebElement chatInput = wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("body > div.client-container > div > div.right > textarea")));
chatInput.sendKeys("测试!");
// 找到发送按钮并点击
WebElement sendButton = wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("body > div.client-container > div > div.right > div.ctrl > button")));
sendButton.click();
}
/**
* 测试聊天接收功能
* 参数:登录 zhaoliu 查看与 zhangsan 历史会话,检验是否发送成功
*/
public void TalkAcceptTest() throws InterruptedException {
//打开页面
driver.get(url2);
driver.findElement(By.cssSelector("#username")).sendKeys("zhaoliu");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
driver.findElement(By.cssSelector("#submit")).click();
//确认弹窗
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
alert.accept();
// 点击联系人
WebElement conversationList = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='client-container']/div/div[@class='left']/div[@class='tab']/div[@class='tab-friend']")));
conversationList.click();
// 重新定位并点击与 zhangsan 的对话
WebElement receiverConversation = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//*[@id='friend-list']/li/h4")));
receiverConversation.click();
//获取好友名
WebElement nameElement = wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#session-list > li > h3")));
String name = nameElement.getText();
//获取最后一条消息
WebElement textElement = wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("#session-list > li > p")));
String text = textElement.getText();
//判断好友是否一致
assert "zhangsan".equals(name) : "好友名不一致,实际为: " + name;
// 判断消息是否一致
assert "测试!".equals(text) : "消息内容不一致,实际为: " + text;
}
}
方法通过继承 Utils 类提高了代码的复用性
ClientNoLogin 方法用于测试未登录状态下访问联系人页面的情况。
elementFind 方法通过 CSS 选择器定位页面上的多个元素, 检查页面元素是否存在。
使用 WebDriverWait 和 ExpectedConditions 结合的方式实现显式等待,确保在操作元素时元素已经处于可点击或可见状态。
testChatSending 方法模拟用户在聊天界面中选择联系人、输入消息并点击发送按钮的操作,测试聊天发送功能。
TalkAcceptTest 方法在登录后,选择特定联系人,获取好友名和最后一条消息,并通过断言判断好友名和消息内容是否符合预期,测试聊天接收功能。最后使用断言来判断目标元素是否一致