1. 项目背景
在数字化浪潮重塑人类社交模式的当下,互联网已成为跨越地域与文化的核心交流载体。尽管新兴社交媒体不断涌现,但传统论坛凭借其深度讨论、知识沉淀和兴趣社群聚合的独特优势,依然在技术极客、学术研究、垂直爱好等领域占据不可替代的地位。面对碎片化信息时代用户对高质量内容交互的迫切需求,构建兼具开放性与秩序性的新型论坛平台势在必行。
比特论坛应运而生,致力于打造综合性的论坛,提供开放的交流空间。
2. 项目功能
本论坛主要实现了登录和注册账号,浏览他人和自己的帖子,修改个人信息和夜间模式,站内私信等等。
- 登录和注册功能:输入论坛的网址,进入论坛的登录页面,点击注册按钮按照要求注册账号。账号注册完成之后返回登录页面进行登录,进入论坛首页。
- 发布和查看帖子功能:在论坛首页点击发布帖子按钮进入帖子发布页面,选择发布的板块,输入帖子的标题和内容之后,可以发布帖子,发布完成之后返回论坛首页,并且刚刚发布的帖子出现在最上面;可以在论坛首页预览他人发布的帖子,也可以点击帖子的标题进入该帖子的详情页面。
- 点赞和评论帖子功能:进入他人发布的帖子详情页面,可以对该帖子进行点赞和评论,也可以私信发帖人。
- 帖子管理和个人信息编辑功能:在论坛首页,点击右上角个人头像,点击我的帖子可以进入我的帖子页面管理帖子,也可以点击个人主页进入个人主页页面修改个人信息。
3. 测试计划
- 依据每个页面的功能和特定的元素进行测试
- 对于有 bug 的进行记录
- 编写自动化测试代码
- 总结本次测试
- 测试环境: windows 11、 IIntelliJ IDEA、JDK17
浏览器 :chrome 134
测试技术:自动化测试 - gitee 链接:https://gitee.com/idzgq/software-test
4. 编写web测试用例
5. 创建空项目
5.1 导入 pom.xml 依赖
<dependencies>
// selenium的依赖
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.29.0</version>
</dependency>
//webdriver 的依赖
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.8.0</version>
</dependency>
//屏幕截图的依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
创建驱动对象 — 所有驱动对象共用一个 driver 对象
5.2 在common中的Utils类创建公共部分的代码
- 创建驱动对象、等待、屏幕截图
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 org.openqa.selenium.chromium.ChromiumDriver;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Duration;
public class Utils {
public static WebDriver driver;
//创建驱动
public static WebDriver createDriver(){
if(driver == null){
WebDriverManager.chromiumdriver().setup();
ChromeOptions options = new ChromeOptions();
//允许访问所有链接
options.addArguments("--remote-allow-origins=*");
driver = new ChromeDriver(options);
//等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
}
return driver;
}
//构造方法
public Utils(String url){
driver = createDriver();
driver.get(url);
}
// 屏幕截图方法
public void getScreenShot(String str) throws IOException {
// 创建日期格式器(年月日格式,用于目录分类)
SimpleDateFormat sim1 = new SimpleDateFormat("yyyy-MM-dd");
// 创建时间格式器(时分秒毫秒格式,用于文件唯一标识)
SimpleDateFormat sim2 = new SimpleDateFormat("HHmmssSS");
// 生成日期目录名(示例:2023-08-25)
String dirTime = sim1.format(System.currentTimeMillis());
// 生成时间文件名(示例:142305899 表示14时23分05秒899毫秒)
String fileTime = sim2.format(System.currentTimeMillis());
// 构建完整文件路径(格式:./src/test/image/日期目录/自定义前缀-时间戳.png)
String fileName = "./src/test/image/"+dirTime+"/"+str+"-"+fileTime+".png";
// 执行屏幕截图操作(需要强制类型转换为TakesScreenshot接口)
File srcFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
// 将截图文件保存到目标路径(使用Apache Commons IO的FileUtils工具类)
// 注意:需确保目标目录已存在,否则会抛出IOException
FileUtils.copyFile(srcFile,new File(fileName));
}
}
5.2 登陆测试(LoginPage)
package tests;
import common.Utils;
import org.openqa.selenium.By;
import java.io.IOException;
public class LoginPage extends Utils {
//通过这种方式获取 url
public static String url = "http://127.0.0.1:9580/sign-in.html";
public LoginPage() {
super(url);
}
/**
* 检查登录页面页面是否加载成功
* 通过查看页面元素是否存在来检查页面加载成功与否
*/
public void loginPageRight(){
driver.findElement(By.cssSelector("body > div.page.page-center > div > div > div:nth-child(1) > div > div.card.card-md > div > h2"));
driver.findElement(By.cssSelector("#username"));
}
//测试登录成功
public static void loginSuc(){
//保证页面没有输入
driver.navigate().refresh();
//输入账号密码
driver.findElement(By.cssSelector("#username")).sendKeys("zgq");
driver.findElement(By.cssSelector("#password")).sendKeys("123456");
//点击登录
driver.findElement(By.cssSelector("#submit")).click();
//检查点击登录后是否登录成功
//法一,测试页面特有的元素是否存在
driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post"));
driver.navigate().back();
}
//测试登录失败
public void loginFail() throws IOException, InterruptedException {
//保证页面没有输入
driver.navigate().refresh();
//检测是否回到了登录界面
driver.findElement(By.cssSelector("#submit"));
//输入账号密码
driver.findElement(By.cssSelector("#username")).sendKeys("admin");
driver.findElement(By.cssSelector("#password")).sendKeys("123");
//点击登录
driver.findElement(By.cssSelector("#submit")).click();
//若登陆失败,则会停留在登录界面,并提示用户名和密码错误并截图
//定位登录按钮
driver.findElement(By.cssSelector("#submit"));
//截图
Thread.sleep(500);
getScreenShot(getClass().getName());
}
}
5.4 注册测试(RegisterPage)
package tests;
import common.Utils;
import org.openqa.selenium.By;
import java.io.IOException;
public class RegisterPage extends Utils {
//通过这种方式获取 url
public static String url = "http://127.0.0.1:9580/sign-up.html";
public RegisterPage() {
super(url);
}
public void RegisterPageRight(){
driver.findElement(By.cssSelector("#signUpForm > div > h2"));
}
//注册成功
public void registerSuc() throws InterruptedException, IOException {
//清除页面填写,确保页面没有填写
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#nickname")).clear();
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#passwordRepeat")).clear();
//依次填写所需要的元素
driver.findElement(By.cssSelector("#username")).sendKeys("doiOJAdjadjo");
driver.findElement(By.cssSelector("#nickname")).sendKeys("asdad");
driver.findElement(By.cssSelector("#password")).sendKeys("123456");
driver.findElement(By.cssSelector("#passwordRepeat")).sendKeys("123456");
//勾选用户协议
driver.findElement(By.cssSelector("#policy")).click();
//点击注册
driver.findElement(By.cssSelector("#submit")).click();
//预期是回到了用户登录的首页,那么检测一下 “用户登录” 这一特殊元素
driver.findElement(By.cssSelector("body > div > div > div > div:nth-child(1) > div > div.card.card-md > div > h2"));
//强制等待0.5s,来截图
Thread.sleep(500);
getScreenShot(getClass().getName());
}
//注册失败 -- 密码和确认密码不同
public void registerFail() throws InterruptedException, IOException {
driver.findElement(By.cssSelector("body > div > div > div > div:nth-child(1) > div > div.text-center.text-muted.mt-3 > a")).click();
driver.findElement(By.cssSelector("#signUpForm > div > h2"));
//清除页面填写,确保页面没有填写
driver.findElement(By.cssSelector("#username")).clear();
driver.findElement(By.cssSelector("#nickname")).clear();
driver.findElement(By.cssSelector("#password")).clear();
driver.findElement(By.cssSelector("#passwordRepeat")).clear();
//依次填写所需要的元素
driver.findElement(By.cssSelector("#username")).sendKeys("hdai");
driver.findElement(By.cssSelector("#nickname")).sendKeys("hdah");
driver.findElement(By.cssSelector("#password")).sendKeys("123456");
driver.findElement(By.cssSelector("#passwordRepeat")).sendKeys("12345");
//勾选用户协议
driver.findElement(By.cssSelector("#policy")).click();
//点击注册
driver.findElement(By.cssSelector("#submit")).click();
//预期是注册失败,那么强制等待 0.5s ,进行截图
Thread.sleep(500);
getScreenShot(getClass().getName());
driver.findElement(By.cssSelector("body > div > div > div.text-center.text-muted.mt-3 > a")).click();
}
}
5.5 论坛首页(ForumHomePage)
package tests;
import common.Utils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import javax.swing.*;
public class package tests;
import common.Utils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import javax.swing.*;
public class ForumHomePage extends Utils {
//通过这种方式获取 url
public static String url = "http://127.0.0.1:9580/index.html";
public ForumHomePage() {
super(url);
}
public void ForumHomePageRight(){
driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post"));
}
public void post_message_test() throws InterruptedException {
//发布帖子
driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")).click();
//选项框
driver.findElement(By.cssSelector("#article_post_borad")).click();
//选择灌水区
driver.findElement(By.cssSelector("#article_post_borad > option:nth-child(9)")).click();
//输入标题
driver.findElement(By.cssSelector("#article_post_title")).sendKeys("3.201");
//输入内容(这边因为是插件过来的所以不可以直接查找并sendkeys())
/**
* 解决方法1:因为本身就有可以直接提交;
* 解决方法2:通过键盘操作来实现;
*/
WebElement ele = driver.findElement(By.cssSelector("#edit-article > div.CodeMirror.cm-s-default.CodeMirror-wrap.CodeMirror-empty > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre"));
Actions actions = new Actions(driver);
actions.doubleClick(ele).perform();
actions.moveToElement(ele).sendKeys("发布成功").perform();
//滚动到页面底部
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
Thread.sleep(2000);
//点击发表
driver.findElement(By.cssSelector("#article_post_submit")).click();
}
//浏览页面
public void browsePageTest() throws InterruptedException {
Thread.sleep(3000);
//点击java
driver.findElement(By.cssSelector("#topBoardList > li:nth-child(2) > a > span.nav-link-title")).click();
//点击C++
driver.findElement(By.cssSelector("#topBoardList > li:nth-child(3) > a > span.nav-link-title")).click();
//点击MySQL
driver.findElement(By.cssSelector("#topBoardList > li:nth-child(5) > a > span.nav-link-title")).click();
//看夜间模式是否有用
driver.findElement(By.cssSelector("body > div.page > header.navbar.navbar-expand-md.navbar-light.d-print-none > div > div > div:nth-child(2) > a.nav-link.px-0.hide-theme-dark > svg"));
//看站内信是否有用
driver.findElement(By.cssSelector("body > div.page > header.navbar.navbar-expand-md.navbar-light.d-print-none > div > div > div:nth-child(2) > div > a > svg"));
//点击排第一个博客
driver.findElement(By.cssSelector("#artical-items-body > div:nth-child(1) > div > div.col > div.text-truncate > a > strong")).click();
//验证用户名、用户头像、点赞和回复按钮是否存在
driver.findElement(By.cssSelector("#article_details_author_name"));
driver.findElement(By.cssSelector("#article_details_author_avatar"));
driver.findElement(By.cssSelector("#details_btn_like_count"));
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
Thread.sleep(2000);
driver.findElement(By.cssSelector("#details_btn_article_reply"));
}
}
extends Utils {
//通过这种方式获取 url
public static String url = "http://127.0.0.1:9580/index.html";
public ForumHomePage() {
super(url);
}
public void ForumHomePageRight(){
driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post"));
}
public void post_message_test() throws InterruptedException {
//发布帖子
driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col-auto.ms-auto.d-print-none > div > a.btn.btn-primary.d-none.d-sm-inline-block.article_post")).click();
//选项框
driver.findElement(By.cssSelector("#article_post_borad")).click();
//选择灌水区
driver.findElement(By.cssSelector("#article_post_borad > option:nth-child(9)")).click();
//输入标题
driver.findElement(By.cssSelector("#article_post_title")).sendKeys("3.201");
//输入内容(这边因为是插件过来的所以不可以直接查找并sendkeys())
/**
* 解决方法1:因为本身就有可以直接提交;
* 解决方法2:通过键盘操作来实现;
*/
WebElement ele = driver.findElement(By.cssSelector("#edit-article > div.CodeMirror.cm-s-default.CodeMirror-wrap.CodeMirror-empty > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre"));
Actions actions = new Actions(driver);
actions.doubleClick(ele).perform();
actions.moveToElement(ele).sendKeys("发布成功").perform();
//滚动到页面底部
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
Thread.sleep(2000);
//点击发表
driver.findElement(By.cssSelector("#article_post_submit")).click();
}
//浏览页面
public void browsePageTest() throws InterruptedException {
Thread.sleep(3000);
//点击java
driver.findElement(By.cssSelector("#topBoardList > li:nth-child(2) > a > span.nav-link-title")).click();
//点击C++
driver.findElement(By.cssSelector("#topBoardList > li:nth-child(3) > a > span.nav-link-title")).click();
//点击MySQL
driver.findElement(By.cssSelector("#topBoardList > li:nth-child(5) > a > span.nav-link-title")).click();
//看夜间模式是否有用
driver.findElement(By.cssSelector("body > div.page > header.navbar.navbar-expand-md.navbar-light.d-print-none > div > div > div:nth-child(2) > a.nav-link.px-0.hide-theme-dark > svg"));
//看站内信是否有用
driver.findElement(By.cssSelector("body > div.page > header.navbar.navbar-expand-md.navbar-light.d-print-none > div > div > div:nth-child(2) > div > a > svg"));
//点击排第一个博客
driver.findElement(By.cssSelector("#artical-items-body > div:nth-child(1) > div > div.col > div.text-truncate > a > strong")).click();
//验证用户名、用户头像、点赞和回复按钮是否存在
driver.findElement(By.cssSelector("#article_details_author_name"));
driver.findElement(By.cssSelector("#article_details_author_avatar"));
driver.findElement(By.cssSelector("#details_btn_like_count"));
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
Thread.sleep(2000);
driver.findElement(By.cssSelector("#details_btn_article_reply"));
}
}
5.6 我的帖子(MyPostPage)
package tests;
import common.Utils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import java.io.IOException;
public class MyPostPage extends Utils{
public static String url = "http://127.0.0.1:9580/index.html";
public MyPostPage() {
super(url);
}
public void MyPostPageRight(){
//点击头像
driver.findElement(By.cssSelector("#index_nav_avatar")).click();
//点击我的帖子
driver.findElement(By.cssSelector("#index_user_profile")).click();
//用户头像、用户名、发帖数、邮箱、注册日期和最底下的个人介绍
driver.findElement(By.cssSelector("#profile_avatar"));
driver.findElement(By.cssSelector("#profile_nickname"));
driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col > div > div:nth-child(1)"));
driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col > div > div:nth-child(2)"));
driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div.col > div > div:nth-child(3)"));
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div.col-lg-4 > div > div > div > div > h2"));
}
//点击跳转测试
public void JumpToDetailTest() throws IOException {
//点击第一个
driver.findElement(By.cssSelector("#profile_article_body > li:nth-child(1) > div.card.timeline-event-card > div > div > div > div > div.text-truncate > a > strong")).click();
//验证用户名、用户头像、点赞和回复按钮是否存在
driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div:nth-child(1) > div.col-3.card > div > h3"));
driver.findElement(By.cssSelector("#article_details_author_avatar"));
driver.findElement(By.cssSelector("#details_btn_like_count"));
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
driver.findElement(By.cssSelector("#details_btn_article_reply"));
getScreenShot(getClass().getName());
}
}
5.7 个人中心(ForumPersonalCenterPage)
package tests;
import common.Utils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import java.io.IOException;
public class ForumPersonalCenterPage extends Utils {
//通过这种方式获取 url
public static String url = "http://127.0.0.1:9580/index.html";
public ForumPersonalCenterPage() {
super(url);
}
public void ForumPersonalCenterPageRight() throws IOException {
//点击头像
driver.findElement(By.cssSelector("#index_nav_avatar")).click();
//点击个人中心
driver.findElement(By.cssSelector("#index_user_settings")).click();
//验证用户中心、我的账户文字和用户头像、用户名、昵称、邮箱电话等信息是否存在
driver.findElement(By.cssSelector("#bit-forum-content > div.page-header.d-print-none > div > div > div > h2"));
driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div > div.col-3.d-none.d-md-block.border-end > div > div > a"));
driver.findElement(By.cssSelector("#settings_nickname"));
driver.findElement(By.cssSelector("#settings_avatar"));
driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div > div.col.d-flex.flex-column > div > h3:nth-child(4)"));
driver.findElement(By.cssSelector("#bit-forum-content > div.page-body > div > div > div > div.col.d-flex.flex-column > div > h3:nth-child(7)"));
getScreenShot(getClass().getName());
}
public void ModifyTest() throws InterruptedException, IOException {
//修改昵称
driver.findElement(By.cssSelector("#setting_input_nickname")).clear();
driver.findElement(By.cssSelector("#setting_input_nickname")).sendKeys("HHH");
driver.findElement(By.cssSelector("#setting_submit_nickname")).click();
//修改邮箱地址
driver.findElement(By.cssSelector("#setting_input_email")).clear();
driver.findElement(By.cssSelector("#setting_input_email")).sendKeys("1123@qq.com");
driver.findElement(By.cssSelector("#setting_submit_email")).click();
//修改电话号码
Thread.sleep(3000);
((JavascriptExecutor) driver).executeScript("window.scrollTo(0, document.body.scrollHeight)");
driver.findElement(By.cssSelector("#setting_input_phoneNum")).clear();
driver.findElement(By.cssSelector("#setting_input_phoneNum")).sendKeys("6666");
driver.findElement(By.cssSelector("#setting_submit_phoneNum")).click();
//修改个人简介
Thread.sleep(3000);
driver.findElement(By.cssSelector("#settings_textarea_remark")).sendKeys("加油");
driver.findElement(By.cssSelector("#settings_submit_remark")).click();
Thread.sleep(3000);
//修改密码
Thread.sleep(3000);
driver.findElement(By.cssSelector("#settings_input_oldPassword")).sendKeys("111111");
driver.findElement(By.cssSelector("#settings_input_newPassword")).sendKeys("123456");
driver.findElement(By.cssSelector("#settings_input_passwordRepeat")).sendKeys("123456");
Thread.sleep(3000);
//点击提交修改
driver.findElement(By.cssSelector("#settings_submit_password")).click();
LoginPage.loginSuc();
//若修改密码成功就会在登录界面
getScreenShot(getClass().getName());
driver.quit();
}
}
5.8 RunTests
import tests.*;
import java.io.IOException;
public class RunTests {
public static void main(String[] args) throws IOException, InterruptedException {
LoginPage loginPage = new LoginPage();
loginPage.loginPageRight();
loginPage.loginSuc();
loginPage.loginFail();
RegisterPage registerPage = new RegisterPage();
registerPage.RegisterPageRight();
registerPage.registerSuc();
registerPage.registerFail();
loginPage.loginSuc();
ForumHomePage forumHomePage = new ForumHomePage();
forumHomePage.ForumHomePageRight();
forumHomePage.post_message_test();
forumHomePage.browsePageTest();
MyPostPage myPostPage = new MyPostPage();
myPostPage.MyPostPageRight();
myPostPage.JumpToDetailTest();
ForumPersonalCenterPage forumPersonalCenterPage = new ForumPersonalCenterPage();
forumPersonalCenterPage.ForumPersonalCenterPageRight();
forumPersonalCenterPage.ModifyTest();
}
}
6. 小结
6.1 测试中的小问题和对应方案
- 页面跳转的时候会发现有的元素无法点击。需要添加等待,确保页面完全加载完成才可以进行后续操作
- 页面有的时候过于长,我们就需要用到模拟鼠标滑轮的操作,将页面移动至指定位置再进行后续操作
- 有的页面抓拍时也需要添加对应的等待时间,以确保页面加载完成才可以完成抓拍
- 结构化的分批次放很有序,方便检查
- 要熟练使用应用抓拍存放的照片的位置和命名,便于寻找,不会重复