目录
4.2.使用Selenium进行Web自动化测试(Python)
[4.2.1 安装 WebDriverManager 和 Selenium](#4.2.1 安装 WebDriverManager 和 Selenium)
[4.2.2 创建测试项目](#4.2.2 创建测试项目)
[4.2.3 编写自动化测试脚本](#4.2.3 编写自动化测试脚本)
1.项目背景
本项目是基于SSM框架(Spring、Spring MVC、MyBatis)开发的共享文章阅读项目,包括用户登录、文章发布、编辑、删除、查看等功能,提供文章列表页和文章详情页展示文章内容。用户登录后可以查看自己和其他用户的文章,并通过系统记录文章的发布时间、标题、发布者信息。项目采用Selenium自动化测试进行功能验证,测试内容涵盖用户登录、文章列表展示、用户信息校验、文章数量统计、查看详情、文章发布与编辑、删除文章等,确保系统的稳定性和功能完整性。
2.编写目的
本报告为MindVault系统的安全测试报告,目的在考察软件安全性,测试结论以及测试建议。
3.测试环境
|-----------------------------------------------------|
| 操作系统:Windows 11 |
| 浏览器及版本:Microsoft Edge版本 135.0.3179.98 (正式版本) (64 位) |
| 网络:WLAN局域网 |
| 设备:PC电脑一台,运行内存:16G,存储空间:1TG |
| 分辨率:2560*1600 |
4.测试内容
4.1.编写测试用例

【1】登录测试
介绍:系统已经在数据库中存储了用户名和密码,用户成功登录后会跳转至文章列表页;执行注销操作后,将返回登录页
测试用例:
(a)界面展示

(b)输入正确的账号和密码(如:账号 zhangsan ,密码 123456)
预期结果:成功登录,并跳转至文章列表页
实际结果:

(c)输入错误的账号或密码(如: 账号 zhangsan ,密码 123)
预期结果:提示"账号或密码错误"
实际结果:

【2】文章列表页及文章数量测试
介绍:登录成功后,系统将跳转至文章列表页面,该页面显示文字的总数量、当前用户的头像和用户名,并提供文章的基本信息(如标题、发布时间及部分内容)
测试用例:
(a)列表页展示(文章数量大于0)

【3】文章详情页测试
介绍:用户点击任意一篇文章的"查看全文"按钮,即可进入文章详情页面,查看完整内容。
-若文章属于当前登录用户,则页面提供"编辑"和"删除"选项
-若文章属于其他用户,则仅支持阅读,不提供编辑和删除权限
测试用例:
(a)详情页界面
(a-1)其他用户的文章详情页
预期结果:页面显示文章标题和内容,不包含"编辑"和"删除"按钮。
实际结果:

(a-2)当前用户的文章详情页
预期结果:页面显示文章标题和内容,并提供"编辑"和"删除"按钮
实际结果:

【4】文章编辑与发布测试
介绍:用户可通过文章列表页右上角的"写文章"按钮进入文章编辑页面,并进行新文章的编写和发布。
测试用例:
(a)登录后跳转至文章列表页

(b)点击"写文章"按钮,跳转至文章编辑页面

(c)输入标题和内容
预期结果:标题和内容不能为空,且输入的内容符合规范。
实际结果:

(d)点击"发布文章"按钮
预期结果:文章成功发布,并跳转回文章列表页;新发布的文章位于列表顶部,并显示文章标题、发布时间及部分内容(若博客内容较长)。

【5】删除文章测试
介绍:在文章列表页面,点击当前用户发布的文章的"查看全文"按钮,进入详情页面后点击"删除"按钮,即可删除该文章,并返回文章列表页。删除后,该文章将不再显示。
测试用例:
(a)文章列表页展示

(b)点击"查看全文"按钮,进入文章详情页

(c)点击"删除"按钮
预期结果:弹窗提醒,确定后文章成功删除,返回文章列表页,且该文章不再显示,文章数量减少1。
实际结果:


【6】退出当前账号测试
介绍:用户在文章列表页面点击"注销"按钮后,将返回登录页面,并清空账号和密码输入框内容。
测试用例:
(a)文章列表页展示

(b)点击右上角"注销"按钮
预期结果:成功退出,跳转至登录页面,且账号和密码输入框为空。
实际结果:

4.2.使用Selenium进行Web自动化测试(Python)
4.2.1 安装 WebDriverManager 和 Selenium
WebDriverManager 是用于管理 Web 驱动程序的工具,在进行 Selenium 测试时,浏览器需要对应的 WebDriver 进行控制和操作。WebDriverManager 能够自动检测本地浏览器版本并下载匹配的驱动程序(如 ChromeDriver、FirefoxDriver 等),避免手动下载和配置的繁琐过程,提高测试效率和稳定性。
4.2.2 创建测试项目
1.在项目中打开 File → Settings,进入设置界面。
2.在"Project: 当前项目名"下,检查是否已安装 selenium
和 webdriver-manager
依赖。

3.若未安装,可使用以下命令进行安装:
pip install selenium webdriver-manager
4.2.3 编写自动化测试脚本
脚本结构:

Utils.py
python
import datetime
import os
import sys
from webdriver_manager.microsoft import EdgeChromiumDriverManager
from selenium import webdriver
from selenium.webdriver.edge.service import Service
class Driver:
driver = None
def __init__(self):
"""初始化WebDriver并启动浏览器"""
options = webdriver.EdgeOptions()
options.page_load_strategy = 'eager'
#options.add_argument('--disable-popup-blocking') # 禁用弹窗拦截
#options.add_argument('--disable-notifications') # 禁用通知弹窗
# options.add_argument('-headless') # 启动无界面模式
edge_driver_path = EdgeChromiumDriverManager().install()
self.driver = webdriver.Edge(service=Service(edge_driver_path), options=options)
def getScreenShot(self):
"""保存当前页面的截图到指定目录"""
dirname = datetime.datetime.now().strftime('%Y-%m-%d')
screenshot_dir = os.path.join(os.path.dirname(os.getcwd()), 'images', dirname)
# 如果截图目录不存在,则创建
if not os.path.exists(screenshot_dir):
os.makedirs(screenshot_dir)
# 根据方法名和时间戳生成唯一的文件名
filename = sys._getframe().f_back.f_code.co_name + "-" + datetime.datetime.now().strftime(
'%Y-%m-%d-%H%M%S') + ".png"
# 保存截图
screenshot_path = os.path.join(screenshot_dir, filename)
self.driver.save_screenshot(screenshot_path)
# 创建BlogDriver实例
BlogDriver = Driver()
RunCase.py
python
import time
from common.Utils import BlogDriver
from cases import BlogLogin, BlogList, BlogEdit, BlogDetail, BlogCancellation
def run_blog_login_tests():
"""运行登录模块的所有测试"""
print("Running BlogLogin tests...")
BlogLogin.BlogLogin().loginSucTest1() # 测试用户名 zhangsan 的登录
BlogLogin.BlogLogin().loginSucTest2() # 测试用户名 lisi 的登录
BlogLogin.BlogLogin().test_login_empty_username_empty_password() # 测试用户名和密码都为空
BlogLogin.BlogLogin().test_login_empty_username_valid_password() # 测试用户名为空,密码有效
BlogLogin.BlogLogin().test_login_invalid_username_invalid_password() # 测试用户名和密码无效
def run_blog_list_tests():
"""运行博客列表页的所有测试"""
print("Running BlogList tests...")
BlogList.BlogList().test_account_name_display() # 测试用户名是否正确显示
BlogList.BlogList().test_article_count_display() # 测试文章数是否正确显示
BlogList.BlogList().test_blog_title_exists() # 测试博客标题是否存在
BlogList.BlogList().test_blog_date_exists() # 测试博客发布日期是否存在
BlogList.BlogList().test_blog_content_exists() # 测试博客内容简介是否存在
BlogList.BlogList().test_blog_button_exists() # 测试博客详情按钮是否存在
BlogList.BlogList().test_navigation_to_blog_editor() # 测试博客编辑页跳转
BlogList.BlogList().test_logout_redirect_to_login_page() # 测试登出后是否跳转到登录页
def run_blog_edit_tests():
"""运行博客编辑页的所有测试"""
print("Running BlogEdit tests...")
BlogEdit.BlogEdit().test_publish_redirect_list() # 测试发布后是否跳转到博客列表页
def run_blog_detail_tests():
"""运行博客详情页的所有测试"""
print("Running BlogDetail tests...")
BlogDetail.BlogDetail().test_current_user_can_edit() # 测试当前用户是否能编辑博客
time.sleep(3)
BlogDetail.BlogDetail().test_current_user_can_delete() # 测试当前用户是否能删除博客
time.sleep(3)
BlogDetail.BlogDetail().test_other_users_cannot_edit_delete() # 测试其他用户是否能编辑或删除博客
time.sleep(3)
def run_blog_cancellation_tests():
"""运行博客注销的所有测试"""
print("Running BlogCancellation tests...")
BlogCancellation.BlogCancellation().test_logout_success() # 测试注销是否成功并限制访问
def run_all_tests():
"""运行所有测试"""
run_blog_login_tests() # 运行登录相关测试
run_blog_list_tests() # 运行博客列表相关测试
BlogLogin.BlogLogin().loginSucTest1() # 重新登录
run_blog_edit_tests() # 运行博客编辑相关测试
run_blog_detail_tests() # 运行博客详情页相关测试
run_blog_cancellation_tests() # 运行博客注销相关测试
print("所有测试已成功通过!")
if __name__ == "__main__":
try:
run_all_tests()
except Exception as e:
print(f"错误发生: {e}")
finally:
BlogDriver.driver.quit() # 结束所有测试后关闭浏览器
BlogLogin.py
python
import time
from common.Utils import BlogDriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoAlertPresentException, TimeoutException
from selenium.webdriver.common.alert import Alert
class BlogLogin:
url = ''
driver = ''
def __init__(self):
"""初始化登录页面并打开指定URL"""
self.url = "http://117.72.87.234:8080/blog_login.html"
self.driver = BlogDriver.driver
self.driver.get(self.url)
def loginSucTest1(self):
"""测试用户 'zhangsan' 的登录成功"""
self._login('zhangsan', '123456')
self._verify_login_success()
self.driver.back()
def loginSucTest2(self):
"""测试用户 'lisi' 的登录成功"""
self._login('lisi', '123456')
self._verify_login_success()
self.driver.back()
def _login(self, username, password):
"""封装的登录方法,接收用户名和密码"""
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, '#username')))
self.driver.find_element(By.CSS_SELECTOR, '#username').clear()
self.driver.find_element(By.CSS_SELECTOR, "#password").clear()
self.driver.find_element(By.CSS_SELECTOR, '#username').send_keys(username)
self.driver.find_element(By.CSS_SELECTOR, "#password").send_keys(password)
self.driver.find_element(By.CSS_SELECTOR, '#submit').click()
# 增加弹窗处理
try:
alert = WebDriverWait(self.driver, 3).until(EC.alert_is_present())
alert_text = alert.text
alert.accept() # 关闭弹窗
return alert_text # 返回弹窗内容(可选)
except:
pass # 如果没有弹窗,继续执行
def _verify_login_success(self):
"""验证用户是否成功登录"""
BlogDriver.getScreenShot()
try:
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, 'body > div.container > div.left > div > h3')))
return True
except TimeoutException:
return False
def test_login_empty_username_empty_password(self):
"""测试用户名和密码为空时的登录行为"""
self._login('', '')
self._verify_alert('登录失败,用户名或密码不能为空')
self.driver.back()
def test_login_empty_username_valid_password(self):
"""测试用户名为空,密码有效时的登录行为"""
self._login('', '123456')
self._verify_alert('登录失败,用户名或密码不能为空')
self.driver.back()
def test_login_empty_username_invalid_password(self):
"""测试用户名为空,密码无效时的登录行为"""
self._login('', '123')
self._verify_alert('登录失败,用户名或密码不能为空')
self.driver.back()
def test_login_invalid_username_empty_password(self):
"""测试用户名无效,密码为空时的登录行为"""
self._login('123', '')
self._verify_alert('登录失败,用户名或密码不能为空')
self.driver.back()
def test_login_valid_username_empty_password(self):
"""测试用户名有效,密码为空时的登录行为"""
self._login('123', '')
self._verify_alert('登录失败,用户名或密码不能为空')
self.driver.back()
def test_login_valid_username_invalid_password(self):
"""测试用户名有效,密码无效时的登录行为"""
self._login('lisi', '123')
self._verify_alert('密码错误')
self.driver.back()
def test_login_invalid_username_valid_password(self):
"""测试用户名无效,密码有效时的登录行为"""
self._login('123', '123456')
self._verify_alert('用户不存在')
self.driver.back()
def test_login_invalid_username_invalid_password(self):
"""测试用户名无效,密码无效时的登录行为"""
self._login('123', '123')
self._verify_alert('用户不存在')
self.driver.back()
def _verify_alert(self, expected_alert_text):
"""验证弹出警告框的文本内容"""
BlogDriver.getScreenShot()
try:
alert = WebDriverWait(self.driver, 5).until(EC.alert_is_present())
assert alert.text == expected_alert_text, f"Expected alert: '{expected_alert_text}', but got: '{alert.text}'"
alert.accept()
return True
except (NoAlertPresentException, TimeoutException):
return False
def test_logout_via_profile(self):
"""测试通过个人资料页面登出"""
self._login('zhangsan', '123456')
self.driver.find_element(By.CSS_SELECTOR, '#submit').click()
self.driver.get('http://117.72.87.234:8080/blog_list.html')
WebDriverWait(self.driver,10).until(EC.presence_of_element_located((By.CSS_SELECTOR,'body > div.nav > a:nth-child(6)')))
self.driver.find_element(By.CSS_SELECTOR,'body > div.nav > a:nth-child(6)').click()
BlogDriver.getScreenShot()
WebDriverWait(self.driver,10).until(EC.presence_of_element_located((By.CSS_SELECTOR,'body > div.container-login > div > h3')))
self.driver.back()
BlogList.py
python
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from common.Utils import BlogDriver
class BlogList:
"""博客列表页测试类"""
url = ''
driver = ''
def __init__(self):
"""初始化 WebDriver 并打开博客列表页"""
self.url = 'http://117.72.87.234:8080/blog_list.html' # 博客列表页 URL
self.driver = BlogDriver.driver # 获取 WebDriver 实例
self.driver.get(self.url) # 打开博客列表页
def test_account_name_display(self):
"""测试用户名称是否正确显示"""
BlogDriver.getScreenShot() # 截图用于调试
user_name = self.driver.find_element(By.CSS_SELECTOR, 'body > div.container > div.left > div > h3').text
assert user_name in ['zhangsan', 'lisi'], f"用户名不匹配,当前显示:{user_name}"
def test_article_count_display(self):
"""测试文章统计信息是否正确显示"""
BlogDriver.getScreenShot()
article_text = self.driver.find_element(By.CSS_SELECTOR, 'body > div.container > div.left > div > div:nth-child(4) > span:nth-child(1)').text
assert article_text == '文章', f"页面上未找到'文章'文本,当前文本:{article_text}"
def test_blog_title_exists(self):
"""测试博客标题是否存在"""
BlogDriver.getScreenShot()
assert self.is_element_present(By.CSS_SELECTOR, 'body > div.container > div.right > div:nth-child(1) > div.title'), "博客标题未找到"
def test_blog_date_exists(self):
"""测试博客发布日期是否存在"""
BlogDriver.getScreenShot()
assert self.is_element_present(By.CSS_SELECTOR, 'body > div.container > div.right > div:nth-child(1) > div.date'), "博客发布日期未找到"
def test_blog_content_exists(self):
"""测试博客内容简介是否存在"""
BlogDriver.getScreenShot()
assert self.is_element_present(By.CSS_SELECTOR, "div.content.markdown-body.editormd-html-preview"), "博客内容简介未找到"
def test_blog_button_exists(self):
"""测试博客详情按钮是否存在"""
BlogDriver.getScreenShot()
assert self.is_element_present(By.CSS_SELECTOR, 'body > div.container > div.right > div:nth-child(1) > a'), "博客详情按钮未找到"
def test_navigation_to_blog_editor(self):
"""测试导航栏跳转到博客编辑页"""
self.driver.find_element(By.CSS_SELECTOR, 'body > div.nav > a:nth-child(4)').click()
try:
WebDriverWait(self.driver, 5).until(EC.title_is('博客编辑页'))
except:
pass
assert self.driver.title == '博客编辑页', f"页面跳转失败,当前页面标题:{self.driver.title}"
def test_logout_redirect_to_login_page(self):
"""测试退出账号是否跳转到登录页"""
self.driver.find_element(By.CSS_SELECTOR, 'body > div.nav > a:nth-child(5)').click()
try:
WebDriverWait(self.driver, 5).until(EC.title_is('博客登陆页'))
except:
pass
assert self.driver.title == '博客登陆页', f"退出登录失败,当前页面标题:{self.driver.title}"
def is_element_present(self, by, selector):
"""检查页面元素是否存在"""
try:
WebDriverWait(self.driver, 5).until(EC.presence_of_element_located((by, selector)))
return True
except:
return False
BlogEdit.py
python
from common.Utils import BlogDriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BlogEdit:
url = ''
driver = ''
def __init__(self):
"""初始化WebDriver并打开博客编辑页面"""
self.url = 'http://117.72.87.234:8080/blog_edit.html'
self.driver = BlogDriver.driver
self.driver.get(self.url)
def test_publish_redirect_list(self):
"""测试发布博客后是否跳转到博客列表页"""
# 输入博客标题
self.driver.find_element(By.CSS_SELECTOR, '#title').send_keys('789')
# 提交博客
self.driver.find_element(By.CSS_SELECTOR, '#submit').click()
BlogDriver.getScreenShot()
# 等待页面跳转到博客列表页,并验证跳转是否成功
try:
WebDriverWait(self.driver, 10).until(EC.title_is('博客列表页'))
except:
pass
# 确保页面标题为'博客列表页'
assert self.driver.title == '博客列表页', f"页面标题错误,实际为:{self.driver.title}"
BlogDetail.py
python
import time
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.alert import Alert
from common.Utils import BlogDriver
from selenium.webdriver.common.by import By
class BlogDetail:
url = ''
driver = ''
def __init__(self):
"""初始化 WebDriver 并打开博客详情页"""
self.url = 'http://117.72.87.234:8080/blog_detail.html?blogId=17' #此处因为程序设计问题每次需要修改blogId的参数,需要注意
self.driver = BlogDriver.driver
self.driver.get(self.url)
def test_current_user_can_edit(self):
"""测试当前用户是否能编辑博客"""
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, 'body > div.container > div.right > div > div.operating > button:nth-child(1)')))
self.driver.find_element(By.CSS_SELECTOR,'body > div.container > div.right > div > div.operating > button:nth-child(1)').click()
BlogDriver.getScreenShot()
# 确保页面跳转到博客编辑页
assert self.driver.title == '博客编辑页', f"编辑页面标题错误:{self.driver.title}"
def test_current_user_can_delete(self):
"""测试当前用户是否能删除博客"""
# 等待删除按钮可点击
WebDriverWait(self.driver,10).until(EC.presence_of_element_located((By.CSS_SELECTOR,'body > div.container > div.right > div > div.operating > button:nth-child(2)')))
self.driver.find_element(By.CSS_SELECTOR,'body > div.container > div.right > div > div.operating > button:nth-child(2)').click()
# 等待弹出确认框
alert = WebDriverWait(self.driver,5).until(EC.alert_is_present())
assert alert.text == '确认删除这篇文章吗?', f"弹窗提示不匹配:{alert.text}"
alert.accept()
BlogDriver.getScreenShot()
# 等待页面跳转至博客列表页
WebDriverWait(self.driver, 10).until(EC.title_is('博客列表页'))
assert self.driver.title == '博客列表页', f"页面标题错误:{self.driver.title}"
def test_other_users_cannot_edit_delete(self):
self.driver.get("http://117.72.87.234:8080/blog_detail.html?blogId=6") #此处是访问非当前用户的博客来进行功能的校验
"""测试其他用户无法编辑或删除博客"""
edit_buttons = self.driver.find_elements(By.CSS_SELECTOR,'body > div.container > div.right > div > div.operating > button:nth-child(1)')
delete_buttons = self.driver.find_elements(By.CSS_SELECTOR,'body > div.container > div.right > div > div.operating > button:nth-child(2)')
BlogDriver.getScreenShot()
# 确保没有编辑和删除按钮
assert not edit_buttons, "其他用户能看到编辑按钮"
assert not delete_buttons, "其他用户能看到删除按钮"
BlogCancellation.py
python
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.Utils import BlogDriver
class BlogCancellation:
url = ''
driver = ''
def __init__(self):
"""初始化 WebDriver 并打开博客列表页"""
self.url = 'http://117.72.87.234:8080/blog_list.html'
self.driver = BlogDriver.driver
self.driver.get(self.url)
def test_logout_success(self):
"""测试用户注销后是否正确跳转回登录页,并限制访问受保护页面"""
# 点击注销按钮
self.driver.find_element(By.CSS_SELECTOR, 'body > div.nav > a:nth-child(5)').click()
BlogDriver.getScreenShot()
# 等待页面跳转到登录页(10秒内出现登录标题)
WebDriverWait(self.driver,10).until(EC.presence_of_element_located((By.CSS_SELECTOR,'body > div.container-login > div > h3')))
# 确保用户名和密码框为空
assert self.driver.find_element(By.CSS_SELECTOR,'#username').get_attribute('value') == ''
assert self.driver.find_element(By.CSS_SELECTOR,'#password').get_attribute('value') == ''
# 测试访问受保护的页面,确保被重定向到登录页
protected_pages = [
"http://117.72.87.234:8080/blog_list.html",
]
for page in protected_pages:
self.driver.get(page)
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, 'body > div.container-login > div > h3'))
)
# 额外检查:尝试编辑博客,确保未登录状态下不能提交
self.driver.get("http://117.72.87.234:8080/blog_edit.html")
try:
self.driver.find_element(By.CSS_SELECTOR, '#title').send_keys('1')
self.driver.find_element(By.CSS_SELECTOR, '#submit').click()
WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, 'body > div.container-login > div > h3')))
except:
print("未登录用户无法提交博客,测试通过")
5.测试内容外的BUG分析
1.该项目中未提供注册功能,只能通过直接操作数据库的方式来增删用户
2.在未登录状态下直接通过url访问文章详情页时发生401错误,原因为代码设计时未能在详情页调用接口被JWT令牌拦截后进行相应处理而造成了该错误
项目及自动化测试脚本源码:MindVault: 该项目搭建的是一个文章分享阅读的平台