一.项目背景
1. 项目简介
本项目是一个基于自研高并发 HTTP 服务器的轻量级 Web 游戏平台,提供贪吃蛇、扫雷、五子棋三款经典小游戏的在线游玩服务。项目采用 C++ 从零实现底层 TCP 网络库和 HTTP 协议解析,实现了完整的用户认证系统、排行榜系统和游戏逻辑。前端采用原生HTML/CSS/JavaScript 构建,配合 Vue 3 等现代前端技术实现动态交互。旨在打造一个高性能、低延迟、可扩展的在线小游戏服务平台。

2. 技术架构
1.底层网络库采用 OneThreadOneLoop 高并发 Reactor 模型(基于 epoll + 事件驱动),由作者自主实现,不依赖任何第三方 Web 框架。HTTP 服务模块在此基础上实现了完整的 HTTP/1.1协议解析(支持 GET/POST/PUT/DELETE/OPTIONS 方法)、路由分发、静态资源服务、会话管理等功能。
2.数据存储方面,采用 Redis 作为用户数据库(支持哨兵模式高可用),存储用户凭证(加盐哈希密码)、会话令牌等关键数据;排行榜数据持久化到本地文件。
-
前端架构,前端采用纯原生 HTML5 + CSS3 + JavaScript (ES6+) 实现,无任何前端框架依赖。页面使用 Flexbox 布局、CSS 渐变/动画、backdrop-filter 玻璃态效果等技术,构建了现代化的深色主题UI。
-
认证体系: a. 客户端密码通过 SHA-256 哈希后传输,杜绝明文传输风险。b. 服务端采用 Salt + SHA-256 双重复杂存储,保障数据库泄露情况下的密码安全。c. 会话管理基于 HttpOnly + SameSite Cookie,防 XSS 和 CSRF 攻击。d. 验证码系统基于 SVG 算术题(加/减/乘法),一次性使用,5 分钟过期。

3. 功能模块
平台目前包含以下核心功能模块:

4. 页面构成

5. 测试背景
随着平台功能逐步完善,各模块的基本逻辑已通过开发阶段的简单验证。为确保平台在生产环境中的稳定性、安全性和用户体验,现针对各个核心登录页面开展全方面系统性测试。测试涵盖功能正确性、界面表现、性能指标、兼容适配、易用性及安全性六个维度,力求从各个角度验证登录模块的质量水准,并为后续其他页面的全面测试奠定方法论基础。
二. 项目功能
1. 用户认证系统
在用户认证方面,平台构建了一套完整且安全的身份验证体系。注册流程要求用户输入用户名和密码,其中密码在前端经过加密传输,服务端使用 SHA-256 加随机 Salt 的方式进行哈希存储,确保用户凭据的安全。为了防止恶意注册,系统限制同一个 IP 地址仅能注册一个账号(测试期间可能会取消限制)。
登录流程在用户输入凭据的基础上,增加了 SVG 格式的算术验证码。验证码采用加减乘三种运算随机组合,每次使用后立即失效,有效防止暴力破解攻击。验证通过后,服务端为用户创建会话 Token,并通过 HttpOnly 属性的 Cookie 下发到浏览器,避免被恶意脚本窃取。
平台还实现了单设备登录保护机制。每个用户的登录状态会记录在 Redis 中,包含登录设备的 IP 地址信息,同时设置一小时的自动过期时间。如果用户已经在某台设备上登录,尝试从其他设备登录时会被系统拒绝,并提示已在其他设备登录。用户需要先在原设备上退出,才能在新设备上登录。在登出时,系统会同时清除服务端的内存会话和 Redis 中的活跃记录,确保状态完全清除。如果用户修改了登录名,活跃会话记录也会自动迁移到新的用户名下。
会话管理方面,用户的登录状态在一小时内无任何操作时会自动过期,既保证了使用便利性,也兼顾了安全性。
2. 游戏系统
平台目前提供三款经典小游戏,覆盖了不同的难度级别和玩法类型。
贪吃蛇作为入门级游戏,采用 Canvas 实时绘制,配合深色主题的视觉风格。玩家可以通过键盘的 WASD 键或方向键控制蛇的移动,在触屏设备上也支持滑动操作。游戏过程中蛇不断前进,吃下食物后身体变长、分数增加。一旦撞到墙壁或自己的身体,游戏即告结束,系统会自动将本次得分提交到排行榜。
扫雷游戏提供了三种难度模式,初级为九乘九的雷区布设十颗地雷,中级为十六乘十六的雷区布设四十颗地雷,高级为三十乘十六的雷区布设九十九颗地雷。玩家通过左键揭开格子,右键标记地雷位置。游戏设计了首次点击保护机制,确保第一步不会踩到地雷,提升了开局体验。右侧面板实时显示游戏用时和剩余雷数,揭开空白区域时系统会自动展开周围的安全格子。
五子棋是平台中唯一一款 AI 对战的游戏。棋盘规格为十五乘十五,玩家执黑子先行,AI 执白子。游戏的核心逻辑会自动检测横、竖、斜四个方向是否有五子连珠的情况,一旦达成立即判定胜负。AI 的基础攻防策略能提供具有一定挑战性的对局体验。
3. 排行榜系统
排行榜是平台的核心功能之一,分为多个维度进行数据展示。每个游戏拥有独立的排行榜,按照玩家的最高得分进行排序。系统会以玩家的唯一标识聚合各次游戏记录,只保留每个玩家的最高得分,避免同一玩家重复刷榜。
排行榜数据持久化存储在本地的文本文件中,系统每隔三十秒自动从文件重新加载数据到内存,保证前端展示的实时性。玩家修改昵称后,排行榜中的显示名会同步更新,无需等待下次提交分数。
在游戏大厅页面,玩家看板会展示全平台所有玩家在各个游戏中的最高得分情况,每五秒自动刷新一次,方便大家随时了解其他玩家的成绩动态。
4.个人中心
个人中心为用户提供了个性化的功能。用户可以上传自己的头像,系统支持 PNG、JPG、GIF、WebP 四种常见图片格式。上传时采用 base64 编码传输,服务端会对文件头进行校验,确保上传的文件确实是合法的图片格式,同时对文件名进行安全过滤,防止路径穿越攻击。
昵称修改功能允许用户更改自己的显示名称,修改后不仅登录用户名会更新,排行榜中的显示名也会同步变化,用户已经提交的历史成绩中的名字也会一并更新。
个人资料页面还会聚合展示用户在各款游戏中的最高得分,并显示账号的注册时间,让用户对自己的游戏历程有一个清晰的了解。
5. 安全特性
在安全方面,平台从多个层面做了防护。用户密码采用加盐哈希存储,即使数据库泄露也无法还原原始密码。会话 Token 通过 HttpOnly Cookie 传递,前端 JavaScript 无法读取,有效防止跨站脚本攻击窃取登录态。验证码一次性使用的设计杜绝了重复提交和暴力破解的可能。
在同一 IP 地址下,系统限制只能注册一个账号,并且同一账号不能同时在多台设备上登录。静态资源的请求路径会经过合法性校验,防止路径穿越攻击。头像上传功能会对文件头进行验证,确保上传的文件确实是图片格式。所有请求参数都做了长度和合法性检查。
6. 高可用架构
为了保证服务的稳定运行,平台在架构层面做了多项设计。Redis 支持 Sentinel 哨兵集群模式,包含三个哨兵节点,能够自动检测主库故障并完成主从切换,避免了单点故障导致服务不可用的情况。
服务本身以守护进程的方式运行在后台,不依赖终端会话。排行榜数据维护线程在后台定时重载,确保数据的一致性。网络层面对长时间没有数据交互的连接,系统会在十秒后自动释放资源,避免无效连接占用服务器资源。
HTTP 服务器采用多线程加 Epoll 事件驱动的并发模型,默认配置五个工作线程处理请求,能够同时处理多个客户端的并发访问,保证游戏的流畅体验。
三.设计测试用例和实施自动化测试
1.Login 页面


1.1 功能测试
1.1 登录成功场景
1.2 登录前端校验拦截
1.3 登录后端校验失败
1.4 验证码功能
1.5 注册功能
1.6 记住密码
1.7 用户存在性检测(blur)
1.8 辅助交互
1.9 单设备登录
1.10 Session管理
1.1.1 登录成功场景

自动化测试代码过长放在仓库内可自行查看。
html
===== 1.1 登录成功场景 =====
--- TC-F-001: 首次正常登录 ---
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[STEP] 打开登录页...
[STEP] 等待验证码加载...
[CHECK] 验证码已加载
[STEP] 输入用户名: sy
[STEP] 输入密码
[STEP] 解析验证码...
[CHECK] 验证码答案: 35
[STEP] 点击登录按钮...
[CHECK] 验证登录按钮loading态...
[INFO] 登录太快,页面已跳转至 /games.html,跳过loading检测
[CHECK] 验证登录成功...
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[PASS] TC-F-001 首次正常登录成功
--- TC-F-004: 已登录访问自动跳转 ---
[STEP] 当前页面: http://175.27.137.241:8081/games.html
[STEP] 访问 login.html(应自动跳转)...
[PASS] 页面自动跳转至: http://175.27.137.241:8081/games.html
[PASS] TC-F-004 已登录访问自动跳转成功
--- TC-F-002: 记住密码自动登录 ---
[STEP] 设置 localStorage rememberedUser
[CHECK] 用户名自动填入: 'sy'
[CHECK] 密码框为空(哈希存储在独立变量)
[CHECK] _savedPasswordHash 已正确加载
[CHECK] 记住密码已自动勾选
[STEP] 等待验证码加载...
[CHECK] 验证码答案: 45
[STEP] 点击登录(使用哈希自动登录)...
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[PASS] TC-F-002 记住密码自动登录成功
--- TC-F-005: 记住密码保存到 localStorage ---
[STEP] 输入用户名: sy
[STEP] 输入密码
[STEP] 勾选记住密码
[CHECK] 验证码答案: 12
[STEP] 点击登录...
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[CHECK] localStorage saved: username=sy, hash=8d969eef6ecad3c2...
[PASS] TC-F-005 记住密码保存到 localStorage 成功
--- TC-F-003: 注册后自动填充登录 ---
[SETUP] 已清除主机注册记录 (host:reg:*)
[STEP] 打开注册模态框...
[STEP] 注册新用户: test_reg_97088
[STEP] 点击注册按钮...
[STEP] 验证注册后自动填充...
[CHECK] 用户名已自动填入: 'test_reg_97088'
[STEP] 等待验证码刷新...
[STEP] 输入密码并填写验证码...
[CHECK] 验证码答案: 30
[STEP] 点击登录(注册后自动填充)...
[FAIL] TC-F-003 注册后自动填充登录: 登录成功验证失败: 无页面跳转且无成功 Toast
--- 1.1 登录成功场景 结果: 4 通过, 1 失败 ---
TC-F-003


注册完成了依旧提示我注册!!!

1.1.2 登录前端校验拦截

html
===== 1.2 登录前端校验拦截 =====
[PASS] TC-F-006 用户名为空 → 前端拦截 '用户名或密码错误'
[PASS] TC-F-007 密码为空 → 前端拦截 '用户名或密码错误'
[PASS] TC-F-008 验证码为空 → 前端拦截 '请完成验证码'
[PASS] TC-F-011 所有输入为空 → 用户名前置拦截
[PASS] TC-F-009 验证码未加载 → 拦截 '验证码加载中,请稍候再试'
[PASS] TC-F-012 重复提交防护 → loginInProgress 拦截成功
[PASS] TC-F-010 密码仅空格 → 后端校验失败 '用户名或密码错误'
--- 1.2 前端校验拦截 结果: 7 通过, 0 失败 ---
1.1.3 登录后端校验失败


html
===== 1.3 登录后端校验失败 =====
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[PASS] TC-F-013 用户不存在 → 蓝色引导消息, 模态框可打开
[PASS] TC-F-014 密码错误 → 显示错误, 验证码已刷新
[PASS] TC-F-015 验证码错误 → 显示错误, 验证码已刷新
[PASS] TC-F-019 login返回非JSON → 显示错误, 验证码已刷新
[PASS] TC-F-020 login网络异常 → 显示错误, 验证码已刷新
[PASS] TC-F-018 网络异常 → 显示错误, 验证码已刷新
--- 1.3 后端校验失败 结果: 6 通过, 0 失败 ---
[MANUAL] 以下 1.3 用例需要手动测试(无法/不适合自动化):
[MANUAL] TC-F-016 验证码过期: 需等待5分钟→手动提交
[MANUAL] TC-F-017 验证码重放: 需截获 captcha_id 重复提交
[MANUAL] TC-F-021 用户名大小写敏感: 需特定混合大小写用户
[MANUAL] TC-F-022 登录中途用户被删除: 时序难以控制
TC-F-016 验证码过期


bug -- 验证码没有失效 -- 登录成功 !!

TC-F-017 验证码重放攻击

第1步:获取验证码(GET)
http://175.27.137.241:8081/api/auth/captcha?id=captcha_replay_001
计算验证码的值,如:14
第2步:第一次登录(POST)
新建一个 POST 请求:
- URL: http://175.27.137.241:8081/api/auth/login
- Headers: 不需要额外添加,Postman 默认的 Content-Type 会自动适配
- Body: 选择 x-www-form-urlencoded(表单格式),填写四个 key-value:

注意:
第一次登录成功后,服务端会给这个 IP 创建活跃 session。第二次再用 sy 登录就可能会触发单设备登录限制。(而不是验证码重放检测)
在第2步和第3步之间把脚本里加上删除步骤,服务端就认为已登出了,第二次登录才会校验验证码。

第3步:第二次登录(POST,重放攻击)

测试成功
TC-F-018 用户名不存在+check-user网络异常


TC-F-021 用户名大小写敏感




用户不存在,提示用户注册

TC-F-022 登录中途用户被后端删除


删除数据库中用户的信息


1.1.4 验证码功能


html
===== 1.4 验证码功能 =====
[PASS] TC-F-023 页面初始化自动加载验证码
[PASS] TC-F-024 点击验证码图片刷新
[PASS] TC-F-025 点击换一张刷新
[PASS] TC-F-026 验证码加法运算
[PASS] TC-F-027 验证码减法运算
[PASS] TC-F-028 验证码乘法运算
[PASS] TC-F-029 验证码加载失败 → 恢复后正常刷新
[PASS] TC-F-033 验证码跨页面独立
[SKIP] TC-F-030 验证码错误后自动刷新 (已在1.3 TC-F-015 中覆盖)
[PASS] TC-F-030 验证码错误后自动刷新 (已在1.3覆盖)
[PASS] TC-F-032 连续快速刷新验证码
--- 1.4 验证码功能 结果: 10 通过, 0 失败 ---
[MANUAL] 以下 1.4 用例需要手动测试:
[MANUAL] TC-F-031 验证码一次性使用: 需截获captcha_id API验证
[MANUAL] TC-F-034 长时间不操作后提交: 需等待5分钟
[MANUAL] TC-F-035 SVG特殊字符转义: 需特殊服务端设置
TC-F-031 验证码一次性使用



TC-F-034 长时间不操作后提交

同TC-F-016 验证码过期
1.1.5 注册功能


html
===== 1.5 注册功能 =====
[PASS] TC-F-036 打开注册模态框
[PASS] TC-F-037 关闭注册×按钮
[PASS] TC-F-038 关闭注册去登录链接
[PASS] TC-F-040 用户名为空或太短
[PASS] TC-F-041 用户名超过32字符
[PASS] TC-F-042 密码为空或小于6位
[PASS] TC-F-043 两次密码不一致
[PASS] TC-F-044 用户名已存在 → '用户名已存在'
[SETUP] 已清除主机注册记录 (host:reg:*)
[PASS] TC-F-039 注册成功
[SETUP] 已清除主机注册记录 (host:reg:*)
[PASS] TC-F-048 注册后自动聚焦密码框
[SETUP] 已清除主机注册记录 (host:reg:*)
[PASS] TC-F-045 同一主机重复注册
[PASS] TC-F-047 注册中重复点击防护
[SETUP] 已清除主机注册记录 (host:reg:*)
[PASS] TC-F-046 注册接口网络异常 → 显示错误, 模态框保持
--- 1.5 注册功能 结果: 13 通过, 0 失败 ---
1.1.6 记住密码


html
===== 1.6 记住密码 =====
[PASS] TC-F-049 记住密码保存
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-F-050 未勾选不保存
[PASS] TC-F-051 再次访问自动填充
[PASS] TC-F-052 记住密码后手动输入新密码
[PASS] TC-F-053 记住的用户后端已删除
[PASS] TC-F-054 localStorage数据损坏
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-F-056 退出登录后记住密码保留
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-F-057 记住密码时密码框聚焦不影响
--- 1.6 记住密码 结果: 8 通过, 0 失败 ---
[MANUAL] TC-F-055 跨浏览器独立性: 需不同浏览器验证
手动测试TC-F-055


我没设置同步edge的数据到Firefox

1.1.7 用户存在性检测(blur)

html
===== 1.7 用户存在性检测(blur) =====
[PASS] TC-F-058 已存在用户名失焦
[PASS] TC-F-059 不存在用户名失焦
[PASS] TC-F-060 太短用户名失焦
[PASS] TC-F-061 用户名输入中隐藏消息
[PASS] TC-F-062 blur时网络异常 → 静默处理, 页面正常
[PASS] TC-F-063 记住密码场景blur检测
--- 1.7 用户存在性检测 结果: 6 通过, 0 失败 ---
1.1.8 辅助交互

html
===== 1.8 辅助交互 =====
[PASS] TC-F-064 键盘Enter提交
[PASS] TC-F-065 忘记密码链接
[PASS] TC-F-066 微信登录按钮
[PASS] TC-F-067 QQ登录按钮
[PASS] TC-F-068 Toast自动消失
--- 1.8 辅助交互 结果: 5 通过, 0 失败 ---
1.1.9 单设备登录

html
===== 1.9 单设备登录 =====
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-F-070 同一IP多次登录
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-F-071 登出后单设备限制解除
--- 1.9 单设备登录 结果: 2 通过, 0 失败 ---
1.1.10 Session管理

html
===== 1.10 Session管理 =====
[SETUP] 已清除主机注册记录 (host:reg:*)
[PASS] TC-F-074 改名后Session同步
--- 1.10 Session管理 结果: 1 通过, 0 失败 ---
[MANUAL] TC-F-072 Session超时自动失效: 登录后等待1小时
[MANUAL] TC-F-073 Cookie大小写兼容: 需抓包验证


1.2 界面测试
界面设计这一块可随产品手册而定,不做过多的标准校验。



html
============================================================
Login页面界面测试 (test_interface)
============================================================
初始化 Edge WebDriver...
Edge WebDriver 初始化成功
===== 2.1 整体布局 =====
[FAIL] TC-U-001 页面居中: 卡片垂直未居中, center_y=416, vp_height/2=296
[PASS] TC-U-003 登录卡片样式
[SKIP] TC-U-002 渐变背景 (需肉眼观察)
[SKIP] TC-U-004 卡片阴影 (需肉眼观察)
[SKIP] TC-U-005 标题文字 (需肉眼观察)
--- 2.1 整体布局 结果: 4 通过, 1 失败 ---
[MANUAL] 以下 2.1 用例需要手动测试:
[MANUAL] TC-U-002 渐变背景: 打开页面肉眼观察深色渐变和径向装饰
[MANUAL] TC-U-004 卡片阴影: 观察 box-shadow 多层阴影和红色辉光
[MANUAL] TC-U-005 标题文字: 观察图标、品牌色(#e94560)是否凸显
[FAIL] 2.1 整体布局 测试失败: 整体布局测试: 1 个用例失败
===== 2.2 表单输入 =====
[FAIL] TC-U-006 输入框默认状态: 输入框文字颜色异常: 'rgb(224, 224, 224)'
[PASS] TC-U-007 输入框聚焦状态
[INFO] placeholder 颜色: rgb(224, 224, 224)
[PASS] TC-U-008 placeholder 颜色
[PASS] TC-U-009 输入框图标
[PASS] TC-U-010 验证码输入框 type/inputmode
[PASS] TC-U-011 验证码SVG渲染
--- 2.2 表单输入 结果: 5 通过, 1 失败 ---
[FAIL] 2.2 表单输入 测试失败: 表单输入测试: 1 个用例失败
===== 2.3 按钮与交互 =====
[PASS] TC-U-012 登录按钮默认
[PASS] TC-U-014 登录按钮disabled
[PASS] TC-U-015 登录按钮loading态
[INFO] shake 检测: {'hasAnimation': False, 'hasShakeClass': False}
[PASS] TC-U-016 shake抖动动画
[INFO] Toast 显示中: class='toast info show', 消失后: class='toast info', visible=False
[PASS] TC-U-019 Toast动画
[SKIP] TC-U-013 登录按钮hover (需肉眼观察)
[SKIP] TC-U-017 cardIn动画 (需肉眼观察)
[SKIP] TC-U-018 社交按钮hover (需肉眼观察)
--- 2.3 按钮与交互 结果: 8 通过, 0 失败 ---
[MANUAL] 以下 2.3 用例需要手动测试:
[MANUAL] TC-U-013 hover: 鼠标悬停登录按钮,观察上移+阴影
[MANUAL] TC-U-017 cardIn: 刷新页面观察卡片从下方淡入
[MANUAL] TC-U-018 社交按钮hover: hover 微信/QQ按钮观察效果
===== 2.4 消息与提示 =====
[INFO] 错误消息: text='用户名或密码错误', class='form-msg error show', bg='rgba(233, 69, 96, 0.1)', color='rgb(233, 69, 96)'
[PASS] TC-U-020 错误消息样式
[INFO] 引导消息: text='您未创建新的账号,请先注册 👆 点击此处跳转注册', cursor='pointer', bg='rgba(66, 153, 225, 0.1)', color='rgb(66, 153, 225)'
[PASS] TC-U-021 引导消息样式
[INFO] loginMsg 初始: display='none', class='form-msg'
[PASS] TC-U-023 消息区域初始隐藏
[SKIP] TC-U-022 Toast色系样式 (需肉眼观察)
--- 2.4 消息与提示 结果: 4 通过, 0 失败 ---
[MANUAL] TC-U-022 Toast色系样式: 触发 success/error/info 三种 Toast,
观察右上角固定位置、圆角、绿/红/蓝色
===== 2.5 注册模态框 =====
[PASS] TC-U-024 遮罩层样式
[FAIL] TC-U-025 模态框卡片: 模态框宽度应在 200-600px 之间, 实际: 1272px
[INFO] 关闭按钮: text='×', color='rgb(85, 85, 102)'
[PASS] TC-U-026 关闭按钮×
[INFO] regError 初始: display='none', content=''
[PASS] TC-U-027 注册错误消息
[SKIP] TC-U-028 模态框动画 (需肉眼观察)
--- 2.5 注册模态框 结果: 4 通过, 1 失败 ---
[MANUAL] TC-U-028 模态框动画: 打开注册模态框观察 cardIn 0.4s ease 动画
[FAIL] 2.5 注册模态框 测试失败: 注册模态框测试: 1 个用例失败
===== 2.6 响应式 =====
[PASS] TC-U-029 桌面端布局
[INFO] 移动端 padding: left=20px, right=20px
[PASS] TC-U-030 移动端布局
[INFO] 移动端 captcha-box 宽度: 100.0px
[PASS] TC-U-031 验证码响应式
[SKIP] TC-U-032 超宽屏 >1920px (需手动调整窗口)
[SKIP] TC-U-033 小高度屏幕滚动 (需手动调整窗口)
--- 2.6 响应式 结果: 5 通过, 0 失败 ---
[MANUAL] 以下 2.6 用例需要手动测试:
[MANUAL] TC-U-032 超宽屏: 窗口 >1920px 观察卡片是否过度拉伸
[MANUAL] TC-U-033 小高度: 窗口高度 <500px 观察内容是否完整可滚动
===== 2.7 边界与溢出 =====
[PASS] TC-U-034 超长用户名截断
[PASS] TC-U-035 密码框遮罩
[PASS] TC-U-036 验证码长度限制
[INFO] 分隔符检测: {'hasOrClass': True, 'hasOrText': True}
[PASS] TC-U-038 分割线或
[SKIP] TC-U-037 社交按钮tooltip (hover 需肉眼观察)
[SKIP] TC-U-039 动画禁用降级 (需 prefers-reduced-motion 环境)
--- 2.7 边界与溢出 结果: 6 通过, 0 失败 ---
[MANUAL] 以下 2.7 用例需要手动测试:
[MANUAL] TC-U-037 社交按钮tooltip: hover 微信/QQ按钮观察 tooltip 淡入淡出
[MANUAL] TC-U-039 动画禁用降级: 系统启用 prefers-reduced-motion 后验证
关闭 WebDriver...
============================================================
测试完成: 通过 4 / 总计 7
失败: 3
============================================================
1.3 安全测试
3.1 密码安全
3.2 会话与Cookie安全
3.3 验证码安全
3.4 输入安全
3.5 请求传输安全
3.6 业务逻辑安全
3.7 路径穿越防护
1.3.1 密码安全

html
===== 6.1 密码安全 =====
[PASS] TC-S-001 密码前端哈希传输 → password 为 64 位 SHA-256
[PASS] TC-S-002 密码服务端加盐存储 → salt + SHA256(salt+pwd)
[SETUP] 已清除主机注册记录 (host:reg:*)
[PASS] TC-S-003 注册同样哈希传输 → password 为 SHA-256
[PASS] TC-S-004 密码框遮罩 → type='password'
[PASS] TC-S-005 记住密码不存明文 → 仅存 hash 字段
[PASS] TC-S-006 长密码无硬编码限制 → 100 位密码可输入
[PASS] TC-S-007 前端SHA-256一致性 → JS 与 Python 结果一致
--- 6.1 密码安全 结果: 7 通过, 0 失败 ---
1.3.2 会话与Cookie安全
html
===== 6.2 会话与Cookie安全 =====
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-S-008 Cookie HttpOnly → 响应头包含 HttpOnly
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-S-009 Cookie SameSite → 响应头包含 SameSite
[SETUP] 已通过 Redis 清除活跃 session
[SETUP] 已通过 Redis 清除活跃 session
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-S-010 Session Token 随机性 → 每次生成唯一 64 位 hex
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-S-012 登出销毁 Session → 旧 token 认证失败
[SETUP] 已通过 API 登出清除活跃 session
[PASS] TC-S-013 登出清除 Cookie → Max-Age=0 清除
[SKIP] TC-S-014 改名后Session同步 (已在 TC-F-074 中覆盖)
--- 6.2 会话与Cookie安全 结果: 6 通过, 0 失败 ---
[MANUAL] TC-S-011 Session超时自动过期: 登录后等待1小时再操作
1.3.3 验证码安全
html
===== 6.3 验证码安全 =====
[SETUP] 已通过 API 登出清除活跃 session
[PASS] TC-S-015 验证码一次性使用 → 重放被拒绝
[PASS] TC-S-016 验证码ID不可预测 → 格式 captcha_ts_random
[PASS] TC-S-018 防OCR识别 → SVG 含旋转+干扰
[SKIP] TC-S-019 未加载验证码防提交 (已在 TC-F-009 中覆盖)
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-S-020 服务端独立验证 → 正确/错误均服务端判断
[PASS] TC-S-021 空 captcha_id 提交 → 服务端拒绝
--- 6.3 验证码安全 结果: 6 通过, 0 失败 ---
[MANUAL] TC-S-017 验证码超时清理: 需等待5分钟后再提交
1.3.4 输入安全
html
===== 6.4 输入安全 =====
[PASS] TC-S-022 XSS用户名含脚本 → 截断/转义, 无弹窗
[SETUP] 已清除主机注册记录 (host:reg:*)
[PASS] TC-S-023 XSS注册含HTML → 转义, 无弹窗
[PASS] TC-S-024 SQL注入 → 无风险, 页面正常处理
[SETUP] 已清除主机注册记录 (host:reg:*)
[PASS] TC-S-025 特殊字符用户名 → 正常注册
[SETUP] 已清除主机注册记录 (host:reg:*)
[PASS] TC-S-026 中文字符用户名 → 正常注册
[PASS] TC-S-027 中文密码 → SHA-256 前后端一致
[PASS] TC-S-028 验证码输入超长数字 → maxlength=4 属性存在
--- 6.4 输入安全 结果: 7 通过, 0 失败 ---
1.3.5 请求传输安全
html
===== 6.5 请求传输安全 =====
[PASS] TC-S-029 同源凭证 → credentials 设置正确
[PASS] TC-S-030 验证码无缓存 → Cache-Control: no-cache, no-store, must-revalidate
[PASS] TC-S-031 URL参数编码 → encodeURIComponent 正常
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-S-032 用户名枚举防护 → 统一错误消息
[NOTE] TC-S-033 check-user 接口安全风险:
[NOTE] check-user 接口会暴露用户名存在性,需注意此攻击面
[NOTE] 建议在生产环境对该接口加频率限制
--- 6.5 请求传输安全 结果: 5 通过, 0 失败 ---
1.3.6 业务逻辑安全
html
===== 6.6 业务逻辑安全 =====
[SETUP] 已通过 API 登出清除活跃 session
[INFO] 已登录自动跳转至 games.html
[INFO] 登录态下注册模态框状态: 不适用(不在登录页)
[PASS] TC-S-034 登录态与注册不冲突
[SKIP] TC-S-035 注册已存在用户 (已在 TC-F-044 中覆盖)
[SKIP] TC-S-036 单IP多账号注册限制 (已在 TC-F-045 中覆盖)
[PASS] TC-S-037 Cookie伪造尝试 → 伪造 token 被拒绝
--- 6.6 业务逻辑安全 结果: 4 通过, 0 失败 ---
[MANUAL] TC-S-038 X-Forwarded-For伪造: 需构造特定 HTTP 请求 验证服务端是否信任了伪造的 IP 头
1.3.7 路径穿越防护
cpp
===== 6.7 路径穿越防护 =====
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-S-039 avatar路径穿越 → 被过滤或安全处理
[PASS] TC-S-040 HTTP路径穿越 → 请求被拒绝 (status=404)
--- 6.7 路径穿越防护 结果: 2 通过, 0 失败 ---


1.4 易用性测试
4.1 输入导航
4.2 信息提示
4.3 交互反馈
4.4 引导与操作
1.4.1 输入导航
html
===== 5.1 输入导航 =====
[INFO] Tab 导航序列: ['username', 'password', 'captchaInput', 'rememberMe', 'forgot-link', 'loginBtn', 'A', 'BODY', 'username', 'password', 'captchaInput', 'rememberMe']
[PASS] TC-UZ-001 Tab键顺序
[PASS] TC-UZ-002 用户名自动填充 (autocomplete='username')
[PASS] TC-UZ-003 密码自动填充 (autocomplete='current-password')
[PASS] TC-UZ-005 Enter提交
[INFO] 验证码输入框 inputmode='numeric' (属性正确,但移动端行为需手动验证)
[SKIP] TC-UZ-004 验证码数字键盘 (需移动端验证)
--- 5.1 输入导航 结果: 5 通过, 0 失败 ---
[MANUAL] 以下 5.1 用例需要手动测试:
[MANUAL] TC-UZ-004 验证码数字键盘: 移动端点击验证码输入框,确认弹出数字键盘
TC-UZ-004

1.4.2 信息提示

html
===== 5.2 信息提示 =====
[INFO] captcha-box title: '点击刷新验证码'
[INFO] captcha-box cursor='pointer', onclick='refreshCaptcha()', tag='div'
[PASS] TC-UZ-006 验证码刷新提示明确
[INFO] 引导消息: '您未创建新的账号,请先注册 👆 点击此处跳转注册'
[INFO] 点击引导消息成功打开注册模态框
[PASS] TC-UZ-007 引导消息可点击
[SETUP] 已清除主机注册记录 (host:reg:*)
[INFO] 注册结果 Toast: '注册成功!请登录'
[INFO] 当前聚焦元素: 'password'
[PASS] TC-UZ-008 注册后流畅衔接
[INFO] Toast 位置: top=24, cardTop=25
[PASS] TC-UZ-009 Toast不遮挡
[INFO] 空用户名错误消息: '用户名或密码错误'
[PASS] TC-UZ-010 错误提示不泄露信息
--- 5.2 信息提示 结果: 5 通过, 0 失败 ---
1.4.3 交互反馈

html
===== 5.3 交互反馈 =====
[INFO] 验证码表达式: '9 × 7 = ?'
[PASS] TC-UZ-011 验证码防滥用且友好
[PASS] TC-UZ-012 刷新验证码自动清空
[INFO] 验证码表达式样本: ['21 + 44 = ?', '10 × 4 = ?', '26 - 25 = ?', '4 × 10 = ?', '9 × 2 = ?', '24 - 7 = ?']
[INFO] 运算符种类: {'-', '×', '+'}
[PASS] TC-UZ-013 验证码多样性
[PASS] TC-UZ-014 记住密码默认不勾选
[INFO] 社交按钮 tooltip 信息: [{'title': '', 'aria': '', 'inner': '💬 微信登录'}, {'title': '', 'aria': '', 'inner': '💬 QQ登录'}]
[PASS] TC-UZ-015 社交按钮tooltip
--- 5.3 交互反馈 结果: 5 通过, 0 失败 ---
1.4.4 引导与操作

html
===== 5.4 引导与操作 =====
[INFO] 忘记密码提示: '请联系管理员重置密码'
[PASS] TC-UZ-016 忘记密码明确提示
[PASS] TC-UZ-017 模态框双关闭方式
[INFO] 注册密码框 placeholder: '至少6位密码'
[PASS] TC-UZ-018 注册密码规则明确
[INFO] body font-family: '"Segoe UI", "Microsoft YaHei", sans-serif'
[INFO] 中文字体: True, 英文字体: True
[PASS] TC-UZ-019 字体清晰
[INFO] fetch 调用时按钮状态: {'cursor': 'not-allowed', 'disabled': True, 'loading': True, 'opacity': '1', 'text': '登 录'}
[PASS] TC-UZ-020 加载反馈明确
[INFO] 注册失败后: 模态框激活=True, 用户名'sy'→'sy', 密码保留=True
[PASS] TC-UZ-021 注册失败保留输入
--- 5.4 引导与操作 结果: 6 通过, 0 失败 ---


1.5 兼容性测试
因时间和设备有限,只作简单的测试。
5.1桌面浏览器
5.2 移动浏览器
5.3 功能兼容
5.4 设备与环境




1.6 性能测试
6.1 页面加载
6.2 接口响应
6.3 并发与压力
6.4 资源与内存
6.5 服务端压力
1.6.1 页面加载



1.6.2 接口响应

TC-P-003 验证码生成响应(平均 ≤ 300ms)

TC-P-003 JMeter:验证码生成性能
目标: 并发调用验证码接口 10 次,平均响应 ≤ 300ms


TC-P-004 check-user 响应(平均 ≤ 100ms)


TC-P-004 JMeter:check-user 响应

TC-P-005 login 接口响应(平均 ≤ 300ms)
在测试这个接口之前需要结合TC-P-003生成验证码



TC-P-005 JMeter:login 接口响应


TC-P-006 register 接口响应(平均 ≤ 300ms)
为了测试这个接口需要修改服务器逻辑,临时开放一个主机只能注册一个账号的限制



TC-P-006 JMeter:register 接口响应



TC-P-007 check 接口响应(平均 ≤ 100ms)



TC-P-007 JMeter:check 接口响应


1.6.3 并发与压力测试

TC-P-008 并发登录(50用户同时登录)
Postman 无法做真正的并发测试,我们使用Jmeter。
目标: 50 个不同用户同时登录,全部成功,无崩溃。
前置准备: 需要提前注册 50 个用户。可用以下 Python 脚本批量注册:
python
import requests
import hashlib
base_url = "http://localhost:8081"
password_hash = hashlib.sha256("123456".encode()).hexdigest()
# 先清除 IP 限制(需要 redis-cli)
import subprocess
subprocess.run(["redis-cli", "DEL", "host:reg:127.0.0.1"])
for i in range(1, 51):
# 获取验证码
captcha_resp = requests.get(f"{base_url}/api/auth/captcha?id=captcha_batch_{i}")
captcha_id = captcha_resp.headers.get("X-Captcha-Id", f"captcha_batch_{i}")
# 注册
resp = requests.post(f"{base_url}/api/auth/register", data={
"username": f"loadtest_user_{i}",
"password": password_hash
})
print(f"Register user_{i}: {resp.json()}")
# 清除 IP 限制以便下一个用户注册
subprocess.run(["redis-cli", "DEL", "host:reg:127.0.0.1"])




TC-P-009 连续刷新验证码 20 次



TC-P-009 JMeter:连续刷新验证码 20 次


TC-P-010 快速提交登录 10 次

同 TC-P-005 login 接口响应
TC-P-010 JMeter:快速提交登录 10 次
同 TC-P-005 JMeter:login 接口响应
TC-P-011 大量用户注册(并发 100 用户)
前置条件:IP 限制已解除



TC-P-012 验证码过期清理开销


TC-P-013

1.6.4 资源与内存测试


1.6.5 服务端压力测试

TC-P-016 SHA-256 计算性能


TC-P-017 JMeter:Session 表高并发(100 线程同时 ValidateSession)






2.Login页面测试总结

3.games页面

3.1 功能测试
页面初始化与登录验证
游戏卡片渲染
游戏导航跳转
玩家信息展示
自动刷新机制
退出登录
导航链接
3.1.1 页面初始化与登录验证

html
===== 1.1 页面初始化与登录验证 =====
--- TC-F-001: 已登录用户正常访问 ---
[SETUP] 测试用户 'sy' 已存在,直接使用
[STEP] 执行登录流程...
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[PASS] 验证1: 当前 URL = http://175.27.137.241:8081/games.html
[PASS] 验证2: 登录界面未渲染
[PASS] 验证3: 渲染了 3 张游戏卡片
[PASS] 验证4: 玩家信息区域存在且可见
[PASS] 验证5: Header 导航链接 4 个
[PASS] 验证6: Footer 可见
[PASS] 验证7: 标题 = '🎮 经典小游戏'
[PASS] TC-F-001 已登录用户正常访问 --- 全部验证通过
--- TC-F-002: 未登录用户访问自动跳转 ---
[STEP] 清除 Cookie 和 localStorage...
[STEP] 访问 games.html (无 session)...
[PASS] 验证: 已自动跳转到 login.html (URL: http://175.27.137.241:8081/login.html)
[PASS] 验证: login.html 已正常加载(验证码可见)
[PASS] TC-F-002 未登录用户访问自动跳转 --- 通过
--- TC-F-005: 页面Title正确 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] 页面 title = '经典小游戏平台'
[PASS] TC-F-005 页面Title正确 --- 通过
--- 1.1 页面初始化 结果: 3 通过, 0 失败 ---
[MANUAL] TC-F-003 Session过期: 需等待1小时后验证
[MANUAL] TC-F-004 check网络异常: 需手动断网验证
TC-F-003 TC-F-004 手动测试通过


3.1.2 游戏卡片渲染
html
===== 1.2 游戏卡片渲染 =====
--- TC-F-006: 游戏卡片数量正确 ---
[PASS] 游戏卡片数量 = 3
[PASS] TC-F-006 游戏卡片数量正确 --- 通过
--- TC-F-007~009: 游戏卡片内容验证 ---
[STEP] 验证卡片1: 贪吃蛇
[CHECK] 卡片1 图标: '🐍'
[CHECK] 卡片1 标题: '贪吃蛇'
[CHECK] 卡片1 描述含关键词
[CHECK] 卡片1 难度: '简单'
[CHECK] 卡片1 模式: '单人'
[CHECK] 卡片1 开始按钮: 可见
[PASS] 卡片1 (贪吃蛇) 全部内容正确
[STEP] 验证卡片2: 扫雷
[CHECK] 卡片2 图标: '💣'
[CHECK] 卡片2 标题: '扫雷'
[CHECK] 卡片2 描述含关键词
[CHECK] 卡片2 难度: '中等'
[CHECK] 卡片2 模式: '单人'
[CHECK] 卡片2 开始按钮: 可见
[PASS] 卡片2 (扫雷) 全部内容正确
[STEP] 验证卡片3: 五子棋
[CHECK] 卡片3 图标: '⚫'
[CHECK] 卡片3 标题: '五子棋'
[CHECK] 卡片3 描述含关键词
[CHECK] 卡片3 难度: '困难'
[CHECK] 卡片3 模式: 'AI对战'
[CHECK] 卡片3 开始按钮: 可见
[PASS] 卡片3 (五子棋) 全部内容正确
[PASS] TC-F-007~009 三张游戏卡片内容全部正确
--- TC-F-010: game-card::after 顶部色条 ---
[CHECK] 卡片1 ::after background = rgb(233, 69, 96)
[CHECK] 卡片2 ::after background = rgb(233, 69, 96)
[CHECK] 卡片3 ::after background = rgb(233, 69, 96)
[PASS] TC-F-010 game-card::after 顶部色条 --- 全部正确
--- TC-F-011: games数组新增游戏自动渲染 ---
[CHECK] 初始卡片数: 3
[CHECK] 新增后卡片数: 4
[CHECK] 新增卡片标题: '俄罗斯方块'
[CHECK] 清理完成,卡片数恢复
[PASS] TC-F-011 games数组扩展自动渲染 --- 通过
--- 1.2 游戏卡片渲染 结果: 4 通过, 0 失败 ---
3.1.3 游戏导航跳
html
===== 1.3 游戏导航跳转 =====
--- TC-F-012: 点击游戏卡片跳转 ---
[PASS] 已跳转到: http://175.27.137.241:8081/snake.html
[PASS] TC-F-012 点击游戏卡片跳转 --- 通过
--- TC-F-013: 点击开始游戏按钮跳转 ---
[PASS] 已跳转到: http://175.27.137.241:8081/snake.html
[PASS] TC-F-013 点击开始游戏按钮跳转 --- 通过
--- TC-F-014: 点击扫雷开始游戏 ---
[PASS] 已跳转到: http://175.27.137.241:8081/minesweeper.html
[PASS] TC-F-014 点击扫雷开始游戏 --- 通过
--- TC-F-015: 点击五子棋开始游戏 ---
[PASS] 已跳转到: http://175.27.137.241:8081/gomoku.html
[PASS] TC-F-015 点击五子棋开始游戏 --- 通过
--- TC-F-016: 游戏子页面未登录访问拦截 ---
[FAIL] TC-F-016: snake.html 请求失败: HTTPConnectionPool(host='175.27.137.241', port=8081): Read timed out.
--- TC-F-017: 已登录访问游戏子页面 ---
[PASS] 已登录访问 snake.html --- 正常显示 (URL: http://175.27.137.241:8081/snake.html)
[PASS] 已登录访问 minesweeper.html --- 正常显示
[PASS] 已登录访问 gomoku.html --- 正常显示
[PASS] TC-F-017 已登录访问游戏子页面 --- 全部通过
--- 1.3 游戏导航跳转 结果: 5 通过, 1 失败 ---
TC-F-16

bug -- 没有之间重定向到login.html

3.1.4 玩家信息展示
html
===== 1.4 玩家信息展示 =====
--- TC-F-018: 玩家数据正常加载 ---
[CHECK] 表头正确: '玩家
🐍 贪吃蛇
💣 扫雷
⚫ 五子棋...'
[CHECK] 玩家数据行数: 1
[CHECK] 第一行数据: 'sy
11
79
208...'
[PASS] TC-F-018 玩家数据正常加载 --- 通过
--- TC-F-022: 未玩游戏的分数显示'-' ---
[PASS] 确认源码含 '-'(未玩) 分数显示逻辑
[CHECK] 数据行内容: ['sy\n11\n79\n208']...
[PASS] TC-F-022 如存在未玩游戏玩家,分数应显示'-'
--- TC-F-023: 玩家名XSS转义 ---
[CHECK] escHtml('<script>...'): '<script>alert(1)</script>'
[CHECK] escHtml('test&user'): 'test&user'
[CHECK] escHtml('test"user'): 'test"user'
[PASS] TC-F-023 玩家名 XSS 防护 --- escHtml 函数正确转义
--- TC-F-024: 玩家列表包含多个玩家 ---
[CHECK] 当前玩家数据行数: 1
[CHECK] 仅有 1 个玩家: 'sy
11
79
208...'
[PASS] TC-F-024 单玩家列表正常渲染
--- 1.4 玩家信息展示 结果: 4 通过, 0 失败 ---
[MANUAL] TC-F-019 玩家数据为空: 需手动清空排行榜后验证
[MANUAL] TC-F-020 玩家数据加载失败: 需手动断网验证
[MANUAL] TC-F-021 API返回非JSON: 需手动模拟服务端异常
[MANUAL] TC-F-025 新玩家无分数: 需注册新用户后不玩任何游戏验证
TC-F-019

操作:后台清空文件,再登录显示:

TC-F-020


TC-F-021

原始:




bug -- 已经正常显示成功,catch成功。
TC-F-025

刚注册没有玩任何游戏,所以没有在玩家榜上出现。

3.1.5 自动刷新机制
html
===== 1.5 自动刷新机制 =====
--- TC-F-026: 每5秒自动刷新玩家数据 ---
[PASS] 验证1: 源码含 setInterval(fetchPlayers, 5000)
[PASS] 验证2: fetchPlayers 函数已定义
[PASS] 验证3: setInterval 已注册, 间隔=5000ms
[PASS] TC-F-026 每5秒自动刷新 --- 通过
--- TC-F-027: 玩家数据自动更新 ---
[STEP] 等待 6 秒 (一个刷新周期)...
[PASS] fetchPlayers 在6秒内被调用 1 次
[PASS] TC-F-027 玩家数据自动更新 --- 通过
--- TC-F-029: fetchPlayers 频次合理 ---
[STEP] 等待 12 秒,监控调用频率...
[CHECK] 12秒内调用次数: 3
[CHECK] 间隔1: 5000ms
[CHECK] 间隔2: 5000ms
[PASS] 平均调用间隔: 5000ms (期望~5000ms)
[PASS] TC-F-029 fetchPlayers 频次合理 --- 通过
--- 1.5 自动刷新机制 结果: 3 通过, 0 失败 ---
[MANUAL] TC-F-028 定时器不泄漏: 需保持页面10分钟以上验证内存
[MANUAL] TC-F-030 刷新期间网络中断: 需手动断网验证
3.1.6 退出登录
1.8 退出登录接口测试 (TC-P相关)

html
===== 1.6 退出登录 =====
--- TC-F-031: 正常退出登录 ---
[CHECK] 退出前 URL: http://175.27.137.241:8081/games.html
[CHECK] 退出登录按钮可见
[CHECK] confirm 弹窗: '您确定要退出登录吗?'
[STEP] 点击 confirm 确定
[PASS] 已退出,跳转到: http://175.27.137.241:8081/login.html
[CHECK] localStorage rememberedUser 已清除
[PASS] TC-F-031 正常退出登录 --- 通过
--- TC-F-032: 取消退出登录 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[CHECK] confirm 弹窗已触发
[PASS] 取消退出,停留在: http://175.27.137.241:8081/games.html
[PASS] TC-F-032 取消退出登录 --- 通过
--- TC-F-034: 退出登录后Session销毁 ---
[CHECK] 当前 session: 8c6b62768a5c86bd3dfa...
[CHECK] 已退出并跳转到 login.html
[CHECK] 退出后 GAME_SESSION cookie: 已清除
[PASS] TC-F-034 退出登录后Session销毁 --- 通过
--- TC-F-035: 退出登录清除Cookie ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[CHECK] 登录态 session: 65d4fd5d02c354c99ac4...
[PASS] Cookie 已失效 --- 访问 games.html 被重定向到 login.html
[PASS] TC-F-035 退出登录清除Cookie --- 通过
--- TC-F-036: 退出登录清除活跃会话 ---
[STEP] 正常登录...
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[CHECK] 第一次登录成功
[CHECK] 已退出
[PASS] 第二次登录成功 --- 活跃会话已被清除
[PASS] TC-F-036 退出登录清除活跃会话 --- 通过
--- TC-F-037: 退出后浏览器后退不返回 ---
[CHECK] 已退出,当前在 login.html
[FAIL] TC-F-037: 后退到 games.html 后应被重定向到 login.html, 当前: http://175.27.137.241:8081/games.html
--- 1.6 退出登录 结果: 5 通过, 1 失败 ---
[MANUAL] TC-F-033 退出时网络异常: 需手动断网验证
[FAIL] 1.6 退出登录 测试失败: 退出登录测试: 1 个用例失败
TC-F-037





3.1.7 导航链接

3.1.8 导航链接
html
===== 1.7 导航链接 =====
--- TC-F-038~040: 导航链接跳转 ---
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[STEP] 测试 排行榜 导航链接...
[CHECK] 排行榜链接可见: '🏆 排行榜'
[PASS] 已跳转到: http://175.27.137.241:8081/leaderboard.html
[STEP] 测试 玩家 导航链接...
[CHECK] 玩家链接可见: '👥 玩家'
[PASS] 已跳转到: http://175.27.137.241:8081/players.html
[STEP] 测试 个人中心 导航链接...
[CHECK] 个人中心链接可见: '👤 个人中心'
[PASS] 已跳转到: http://175.27.137.241:8081/profile.html
[PASS] TC-F-038~040 全部导航链接跳转正确
--- TC-F-043: 未登录页无导航链接 ---
[PASS] login.html 无 games.html 导航链接
[CHECK] login.html 登录卡片正常显示
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] TC-F-043 未登录页无导航链接 --- 通过
--- 1.7 导航链接 结果: 2 通过, 0 失败 ---
[MANUAL] TC-F-041 导航链接hover样式: 需肉眼观察红色高亮
[MANUAL] TC-F-042 退出登录链接红色样式: 需肉眼观察红色文字
3.2 功能测试测试总结


3.1 界面测试 略





3.2 易用性测试 略




3.3 兼容性测试 略




3.4 安全测试
XSS防护
CSRF防护
点击劫持防护
信息泄露
前端逻辑安全
服务端安全
认证与授权
3.4.1 XSS防护

html
============================================================
开始测试: 6.2 XSS防护
============================================================
===== 6.2 XSS防护 =====
--- TC-S-008: 玩家名XSS向量 ---
[STEP] 在浏览器中测试 escHtml() 转义...
[PASS] 向量 '<script>alert('xss')</script>...' → 已转义: '<script>alert('xss')</script>...'
[PASS] 向量 '<img src=x onerror=alert(1)>...' → 已转义: '<img src=x onerror=alert(1)>...'
[PASS] 向量 '<svg onload=alert(1)>...' → 已转义: '<svg onload=alert(1)>...'
[PASS] 向量 'javascript:alert(1)...' → 已转义: 'javascript:alert(1)...'
[PASS] 向量 '<body onload=alert(1)>...' → 已转义: '<body onload=alert(1)>...'
[STEP] 检查页面中 escHtml 函数的使用...
[CHECK] 页面包含 escHtml() 函数定义
[STEP] 检查 innerHTML 使用模式...
[CHECK] 页面中 innerHTML 使用次数: 5
[PASS] 玩家信息区域无未转义的 <script> 标签
[PASS] TC-S-008 玩家名XSS向量 --- 通过
--- TC-S-009: 游戏名XSS(数据源安全) ---
[STEP] 检查游戏数据来源...
[CHECK] 游戏标题: '贪吃蛇'
[CHECK] 游戏标题: '扫雷'
[CHECK] 游戏标题: '五子棋'
[STEP] 检查 JS 源码中 games 数组的定义...
[CHECK] games 数组内容: [{"id":"snake","name":"🐍 贪吃蛇","desc":"经典贪吃蛇游戏,控制蛇吃食物不断成长。撞墙或撞自身则游戏结束,支持键盘和触屏操作。","page":"snake.html","difficulty":"easy","mode":"single","badges":["简单","单人"]},{"id":"minesweeper","name":"💣 扫雷","desc":...
[PASS] games 数组为前端硬编码,无用户输入注入点
[PASS] TC-S-009 游戏名XSS安全 --- 通过
--- TC-S-010: 描述文字XSS ---
[STEP] 检查游戏描述...
[CHECK] 描述: '经典贪吃蛇游戏,控制蛇吃食物不断成长。撞墙或撞自身则游戏结束,支持键盘和触屏操作。...'
[CHECK] 描述: '经典扫雷游戏,在雷区中标记所有地雷。三种难度可选,挑战你的逻辑推理能力。...'
[CHECK] 描述: '经典五子棋,你和 AI 轮流落子,先在横、竖、斜任意方向连成五子者获胜。...'
[PASS] TC-S-010 描述文字XSS安全 --- 通过
--- TC-S-011: innerHTML使用安全 ---
[STEP] 分析 innerHTML 使用情况...
[CHECK] gameGrid: hasDynamicContent=False
[CHECK] playerContent: hasDynamicContent=False
[STEP] 验证 gameGrid 内容来自硬编码数据...
[PASS] gameGrid 无用户可控数据注入
[STEP] 检查动态属性注入风险...
[PASS] gameGrid 无危险协议链接
[PASS] TC-S-011 innerHTML使用安全 --- 通过
[NOTE] 如果未来 games 数组改为从 API 获取,需确保使用 escHtml() 或 textContent
--- TC-S-012: 特殊字符玩家名 ---
[STEP] 测试特殊字符转义...
[CHECK] '&' → '&'
[CHECK] '<' → '<'
[CHECK] '>' → '>'
[CHECK] '"' → '"'
[CHECK] ''' → '''
[CHECK] '/' → '/'
[CHECK] '<script>' → '<script>'
[CHECK] '&' → '&amp;'
[STEP] 检查玩家信息HTML结构...
[CHECK] 玩家信息区域HTML结构: 正常
[PASS] TC-S-012 特殊字符玩家名 --- 通过
--- TC-S-013: 玩家名含emoji ---
[STEP] 测试emoji字符处理...
[PASS] '🎮🔥💣' emoji完整保留
[PASS] '😀😃😄😁' emoji完整保留
[PASS] '玩家🎮名称' emoji完整保留
[PASS] '🎯🏆👑⭐' emoji完整保留
[PASS] 'test🎮<script>alert(1)</script>...' emoji保留 + script转义
[STEP] 检查页面emoji渲染...
[CHECK] 页面游戏图标: 3 个, 包含emoji: True
[PASS] TC-S-013 玩家名含emoji --- 通过
--- 6.2 XSS防护 结果: 6 通过, 0 失败 ---
[MANUAL] TC-S-008 完整验证: 需提交含XSS向量的分数到排行榜,
然后检查 games.html 中玩家名是否被正确转义
[MANUAL] TC-S-012/013 完整验证: 需注册含特殊字符/emoji的用户名,
然后提交分数,验证在排行榜和玩家信息中正确显示
[PASS] 6.2 XSS防护 --- 测试通过
TC-S-008
- 提交包含 XSS 向量的分数



TC-S-012/013


3.4.2 CSRF防护

html
===== 6.3+6.4 CSRF防护 + 点击劫持 =====
--- TC-S-014: 退出登录CSRF ---
[STEP] 检查前端 logout 代码的 fetch 参数...
[CHECK] 使用 'same-origin': True
[CHECK] 使用 'credentials': True
[STEP] 模拟跨站 CSRF 攻击:不带 Cookie POST /api/auth/logout...
[CHECK] CSRF请求状态码: 200
[CHECK] CSRF请求响应: {'status': 'ok', 'message': '已退出登录'}
[WARN] 不带Cookie的logout返回ok(但可能实际上未清除session)
[CHECK] CORS 头部: {'Access-Control-Allow-Origin': None, 'Access-Control-Allow-Credentials': None}
[STEP] 验证 logout API 的参数要求...
[PASS] TC-S-014 退出登录CSRF --- 通过
[NOTE] CSRF 防护关键: 1) ExtractToken 从Cookie提取session
2) 跨域请求默认不携带Cookie(SameSite=Lax)
3) 前端 fetch使用credentials:'same-origin'
--- TC-S-015: 敏感操作需Cookie ---
[STEP] 测试 退出登录 (/api/auth/logout) 不带Cookie...
→ 状态码: 200
[WARN] logout 不带Cookie返回ok(可能清除失败)
[STEP] 测试 认证检查 (/api/auth/check) 不带Cookie...
→ 状态码: 200
[PASS] check 正确返回: {'status': 'error', 'message': '未登录'}
[STEP] 测试 修改用户名 (/api/user/update-username) 不带Cookie...
→ 状态码: 200
[PASS] TC-S-015 敏感操作需Cookie --- 通过
--- TC-S-016: Session Cookie SameSite ---
[STEP] 通过 login API 获取 Set-Cookie 响应头...
[CHECK] 登录API状态码: 200
[CHECK] Set-Cookie 头: GAME_SESSION=6bddcfb6bdcd64f7608aa6ca4726b2106886ec69edb121d9f637c24cb4f7b31d; Path=/; HttpOnly; Sam...
[CHECK] Cookie 属性分析:
HttpOnly: True
SameSite: Lax
Secure: False
Max-Age: 3600
Path: /
[PASS] HttpOnly 已设置 (阻止JS访问Cookie)
[PASS] SameSite=Lax (防CSRF)
[PASS] Path=/ 正确
[PASS] TC-S-016 Session Cookie SameSite --- 通过
3.4.3 点击劫防护
html
--- TC-S-017: 页面可否被iframe嵌入 ---
[STEP] 检查 games.html 的 HTTP 响应头...
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[CHECK] 安全响应头分析:
X-Frame-Options: None
X-Content-Type-Options: None
Content-Security-Policy: None
Server: None
[FIND] 缺少 X-Frame-Options 头 --- 存在点击劫持风险
建议添加: X-Frame-Options: DENY 或 SAMEORIGIN
[FIND] 缺少 Content-Security-Policy 头
[STEP] 尝试检测 iframe 嵌入限制...
[CHECK] iframe嵌入检测: {'accessible': True, 'note': 'iframe可以嵌入同源页面', 'title': '经典小游戏平台'}
[FIND] 页面可被同源iframe嵌入(正常行为)
[PASS] TC-S-017 页面可否被iframe嵌入 --- 通过
[NOTE] 如果缺少 X-Frame-Options,建议添加安全响应头
--- TC-S-018: 退出确认防误触 ---
[STEP] 检查前端 confirm 弹窗代码...
[CHECK] 前端代码包含 confirm: True
[CHECK] 退出按钮事件: has_element
[STEP] 模拟 confirm 返回 false(点击取消)...
[CHECK] 当前 URL: http://175.27.137.241:8081/games.html
[PASS] confirm取消后停留在当前页
[STEP] 验证服务端 logout API 的存在...
[CHECK] 前端调用 /api/auth/logout: True
[PASS] 退出流程包含服务端 API 调用(核心安全保障)
[PASS] TC-S-018 退出确认防误触 --- 通过
[NOTE] confirm 弹窗可被JS绕过,核心安全依赖服务端 DestroySession
--- 6.3+6.4 CSRF + 点击劫持 结果: 5 通过, 0 失败 ---
[MANUAL] TC-S-017 完整验证: 创建独立HTML页面用iframe嵌入games.html
验证是否可被嵌入并操作(点击劫持风险)
[PASS] 6.3+6.4 CSRF+点击劫持 --- 测试通过
============================================================
3.4.4 信息泄露
html
===== 6.5 信息泄露 =====
--- TC-S-019: 响应头信息泄露 ---
[STEP] 检查 /games.html 响应头...
→ 状态码: 302
[PASS] 无 Server 头
[PASS] 无 X-Powered-By 头
[STEP] 检查 /login.html 响应头...
→ 状态码: 200
[PASS] 无 Server 头
[PASS] 无 X-Powered-By 头
[STEP] 检查 /api/auth/check 响应头...
→ 状态码: 200
[PASS] 无 Server 头
[PASS] 无 X-Powered-By 头
[STEP] 检查 /api/players 响应头...
→ 状态码: 200
[PASS] 无 Server 头
[PASS] 无 X-Powered-By 头
[STEP] 检查 / 响应头...
→ 状态码: 302
[PASS] 无 Server 头
[PASS] 无 X-Powered-By 头
[PASS] TC-S-019 响应头信息泄露 --- 通过
--- TC-S-020: 前端代码信息泄露 ---
[STEP] 检查前端源码是否泄露服务端逻辑...
[PASS] 未发现服务端代码泄漏到前端
[STEP] 识别 API 端点暴露情况...
[CHECK] 前端暴露了 3 个API端点
- /api/auth/check
- /api/auth/logout
- /api/players
[PASS] API端点暴露数量合理(属公开接口)
[STEP] 检查敏感配置泄露...
[PASS] 未发现敏感配置泄露
[PASS] 无内网地址泄露
[PASS] TC-S-020 前端代码信息泄露 --- 通过
--- TC-S-021: 错误消息不泄露后端信息 ---
[STEP] 分析前端错误处理逻辑...
[CHECK] 前端错误消息: ['><img src=x onerror=alert(1)></span><span class=', '>⚠️</div><p>玩家信息加载失败</p></div>', '\n });\n } catch (e) {\n // 即使网络请求失败,也清除本地状态并跳转\n }\n // 清除本地记住的密码\n localStorage.removeItem(']
[PASS] 错误消息不包含后端敏感信息
[STEP] 检查 API 错误响应...
[CHECK] check错误消息: '未登录'
[PASS] TC-S-021 错误消息不泄露后端信息 --- 通过
--- TC-S-022: 登录用户信息泄露 ---
[STEP] 检查 /api/auth/check 返回的字段...
[CHECK] /api/auth/check 响应: {'status': 'ok', 'username': 'sy'}
[PASS] check响应仅返回 status 和 username
[STEP] 检查前端对check响应的使用...
[STEP] 检查 localStorage 中的敏感信息...
[CHECK] localStorage 内容: {}
[PASS] localStorage 无敏感信息存储
[PASS] TC-S-022 登录用户信息泄露 --- 通过
--- 6.5 信息泄露 结果: 4 通过, 0 失败 ---
[PASS] 6.5 信息泄露 --- 测试通过
3.4.5 前端逻辑安全
html
===== 6.6 前端逻辑安全 =====
--- TC-S-023: 前端跳转可被绕过 ---
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 获取到 session: f51032fbb1e5668ded91...
[STEP] 带有效 Cookie 通过 curl 方式请求 games.html...
[CHECK] 带有效Cookie的状态码: 200
[PASS] 带有效Cookie直接获取页面成功(服务端认证通过)
[CHECK] 返回内容确认为 games.html
[STEP] 不带 Cookie 请求验证认证拦截...
[PASS] 不带Cookie被正确重定向
[STEP] 验证前端JS的认证检查...
[CHECK] 前端 init() 函数存在: True
[CHECK] 前端有双重认证检查 (服务端中间件 + JS init)
[PASS] TC-S-023 前端跳转可被绕过 --- 通过
[NOTE] 这不是漏洞:认证主要由服务端 AuthCheckMiddleware 完成
前端JS跳转仅为辅助,服务端中间件是真正的安全边界
--- TC-S-024: localStorage清除可被绕过 ---
[STEP] 检查登录状态下的 localStorage...
[CHECK] rememberedUser: None
[STEP] 模拟攻击者阻止跳转场景...
[CHECK] 阻止跳转后 rememberedUser: None
[PASS] 即使阻止跳转,localStorage已先被清除
[PASS] TC-S-024 localStorage清除可被绕过 --- 通过
[NOTE] 代码先执行 removeItem 再跳转,即使跳转被阻止,
localStorage 中也已无用户数据
--- TC-S-025: confirm弹窗欺骗 ---
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 获取到 session: 9fa1d59f95debe95c516...
[STEP] 绕过 confirm 直接调用退出流程...
[PASS] confirm被绕过但退出仍成功(服务端session已清除)
[STEP] 验证服务端 session 是否被清除...
[PASS] 服务端 session 已被销毁(核心安全机制生效)
[PASS] TC-S-025 confirm弹窗欺骗 --- 通过
[NOTE] confirm弹窗是UI层保护(防误触),非安全边界
真正的安全机制在服务端:DestroySession(token)
--- TC-S-026: 定时器劫持 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[STEP] 检查定时器设置...
[CHECK] setInterval 使用: True
[CHECK] fetchPlayers 函数: True
[CHECK] /api/players 端点: True
[STEP] 分析定时器请求的数据...
[CHECK] 定时器拦截配置: {'hasCredentials': None, 'intercepted': False, 'method': None, 'url': None}
[STEP] 等待定时器触发...
[STEP] 检查 /api/players 响应数据结构...
[CHECK] /api/players 响应键: ['status', 'players']
[PASS] 定时器传输的数据不包含敏感信息
[STEP] 检查定时器请求的 Cookie 使用...
[CHECK] 使用credentials: True
[CHECK] 使用same-origin: True
[PASS] TC-S-026 定时器劫持 --- 通过
[NOTE] 定时器仅调用 /api/players 获取排行榜数据用于展示
不传输用户认证信息或敏感数据
--- 6.6 前端逻辑安全 结果: 4 通过, 0 失败 ---
[MANUAL] TC-S-025 完整验证: 使用浏览器开发者工具重写confirm,
然后点击退出按钮,验证服务端session是否被清除
[PASS] 6.6 前端逻辑安全 --- 测试通过
3.4.6 服务端安全
html
===== 6.7 服务端安全 =====
--- TC-S-027: 路径穿越防护 ---
[STEP] 测试路径穿越: '/games.html/../../../etc/passwd...'
→ 状态码: 404
[PASS] 路径穿越被阻止 (状态码: 404)
[STEP] 测试路径穿越: '/../../../etc/passwd...'
→ 状态码: 404
[PASS] 路径穿越被阻止 (状态码: 404)
[STEP] 测试路径穿越: '/%2e%2e/%2e%2e/%2e%2e/etc/passwd...'
→ 状态码: 404
[PASS] 路径穿越被阻止 (状态码: 404)
[STEP] 测试路径穿越: '/..%2f..%2f..%2fetc/passwd...'
→ 状态码: 404
[PASS] 路径穿越被阻止 (状态码: 404)
[STEP] 测试路径穿越: '/....//....//....//etc/passwd...'
→ 状态码: 404
[PASS] 路径穿越被阻止 (状态码: 404)
[STEP] 测试路径穿越: '/..\..\..\windows\system32\config\sam...'
→ 状态码: 404
[PASS] 路径穿越被阻止 (状态码: 404)
[STEP] 测试路径穿越: '/games.html%00/../../../etc/passwd...'
→ 状态码: 404
[PASS] 路径穿越被阻止 (状态码: 404)
[STEP] 测试路径穿越: '//..//..//..//etc/passwd...'
→ 状态码: 404
[PASS] 路径穿越被阻止 (状态码: 404)
[STEP] 测试路径穿越: '/./.././../etc/passwd...'
→ 状态码: 404
[PASS] 路径穿越被阻止 (状态码: 404)
[CHECK] 所有路径穿越攻击被阻止: True
[CHECK] 正常games.html请求: 302
[PASS] TC-S-027 路径穿越防护 --- 通过
[NOTE] ValidPath 函数有效防止 ../ 路径穿越攻击
--- TC-S-028: 静态文件目录限制 ---
[STEP] 尝试越界访问: '/../source/http/game.cpp'
[PASS] 越界访问被拒绝 (状态码: 404)
[STEP] 尝试越界访问: '/../source/http/auth.hpp'
[PASS] 越界访问被拒绝 (状态码: 404)
[STEP] 尝试越界访问: '/../source/http/http.hpp'
[PASS] 越界访问被拒绝 (状态码: 404)
[STEP] 尝试越界访问: '/../source/http/makefile'
[PASS] 越界访问被拒绝 (状态码: 404)
[STEP] 尝试越界访问: '/../test_lists/games/test_cases_games_xmind.md'
[PASS] 越界访问被拒绝 (状态码: 404)
[STEP] 尝试越界访问: '/etc/passwd'
[PASS] 越界访问被拒绝 (状态码: 404)
[STEP] 尝试越界访问: '/etc/shadow'
[PASS] 越界访问被拒绝 (状态码: 404)
[STEP] 尝试越界访问: '/proc/self/environ'
[PASS] 越界访问被拒绝 (状态码: 404)
[PASS] TC-S-028 静态文件目录限制 --- 通过
--- TC-S-029: 请求方法限制 ---
[STEP] POST请求.html文件: POST /games.html
→ 状态码: 404
[PASS] POST方法被拒绝
[STEP] PUT请求.html文件: PUT /games.html
→ 状态码: 404
[PASS] PUT方法被拒绝
[STEP] DELETE请求.html文件: DELETE /games.html
→ 状态码: 404
[PASS] DELETE方法被拒绝
[STEP] PATCH请求.html文件: PATCH /games.html
→ 状态码: 400
[CHECK] PATCH方法返回 400
[STEP] OPTIONS请求.html文件: OPTIONS /games.html
→ 状态码: 400
[CHECK] OPTIONS方法返回 400
[STEP] TRACE请求.html文件: TRACE /games.html
→ 状态码: 400
[CHECK] TRACE方法返回 400
[STEP] CONNECT请求.html文件: CONNECT /games.html
→ 状态码: 400
[CHECK] CONNECT方法返回 400
[CHECK] GET /games.html: 302
[PASS] TC-S-029 请求方法限制 --- 通过
--- TC-S-030: URL超长拒绝 ---
[STEP] 发送 8512 字符的 URL...
[STEP] 使用 raw socket 发送超长请求...
→ 状态码: 414
[PASS] Socket超长URL返回 414 URI Too Long
[PASS] TC-S-030 URL超长拒绝 --- 通过
--- TC-S-031: Header超长拒绝 ---
[STEP] 发送 Header 超长 (8300 字符) 的请求...
→ 状态码: 414
[PASS] 超长Header请求被拒绝 (状态码: 414)
[PASS] TC-S-031 Header超长拒绝 --- 通过
--- TC-S-032: 并发连接耗尽防护 ---
[STEP] 发送 30 个并发请求...
[CHECK] 完成时间: 0.06s
[CHECK] 成功: 30, 失败: 0
[STEP] 验证服务器在并发后仍然正常...
[CHECK] 并发后请求状态码: 302
[PASS] 服务器在并发后仍正常响应
[CHECK] 成功率: 100.0%
[PASS] TC-S-032 并发连接耗尽防护 --- 通过
--- TC-S-033: games数组仅在前端 ---
[STEP] 检查 games 数组定义...
[CHECK] games数据来源: {'count': 3, 'source': 'global variable'}
[PASS] games 数组在前端JS全局变量中硬编码
[STEP] 验证游戏数据不从API获取...
[PASS] 无获取游戏列表的API调用
[STEP] 验证游戏信息为硬编码值...
[CHECK] 游戏: id=snake, name=🐍 贪吃蛇, icon=None
[CHECK] 游戏: id=minesweeper, name=💣 扫雷, icon=None
[CHECK] 游戏: id=gomoku, name=⚫ 五子棋, icon=None
[PASS] 所有游戏数据为硬编码值
[PASS] TC-S-033 games数组仅在前端 --- 通过
--- 6.7 服务端安全 结果: 7 通过, 0 失败 ---
[MANUAL] TC-S-032 完整验证: 使用专业压测工具(如 ab, wrk)
测试500+并发连接,监控服务器CPU/内存使用和响应情况
[PASS] 6.7 服务端安全 --- 测试通过
3.4.7 认证与授权

html
===== 6.1 认证与授权 =====
--- TC-S-001: 未登录直接访问games.html ---
[STEP] 不带 Cookie 请求 games.html...
[CHECK] 响应状态码: 302
[PASS] 验证1: 返回 302 重定向
[PASS] 验证2: Location = '/login.html'
[PASS] 验证3: 未返回页面内容
[STEP] Selenium 验证: 清除 Cookie 后访问 games.html...
[PASS] Selenium验证: 已自动跳转到 login.html
[PASS] TC-S-001 未登录直接访问games.html --- 通过
--- TC-S-002: 伪造Session Cookie访问 ---
[STEP] 使用伪造 Cookie: GAME_SESSION=aaaaaaaaaaaaaaaaaaaa...
[CHECK] 响应状态码: 302
[PASS] 验证1: 返回 302 重定向
[PASS] 验证2: Location = '/login.html'
[STEP] 测试多种伪造 token 格式...
[PASS] 验证3: 多种伪造 token 均被拒绝
[PASS] TC-S-002 伪造Session Cookie访问 --- 通过
--- TC-S-003: 过期Session访问 ---
[STEP] 使用过期格式的 token 访问 games.html...
[CHECK] 响应状态码: 302
[PASS] 验证: 过期 token 被拒绝 (302)
[PASS] 验证: 空 session 也被拒绝
[PASS] TC-S-003 过期Session访问 --- 通过
[NOTE] 完整测试需等待1小时后刷新页面,验证自动跳转到 login.html
--- TC-S-004: 登出后Session立即失效 ---
[STEP] 登录并获取有效 session...
[SETUP] 测试用户 'sy' 已存在,直接使用
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 获取到 session: 57216d5a23aab30881a3...
[CHECK] 有效 session: 57216d5a23aab30881a3...
[STEP] 验证 token 当前有效...
[CHECK] 有效token访问状态码: 200
[STEP] 执行登出操作...
[WARN] Selenium 未检测到跳转,继续验证...
[STEP] 用旧 token 再次访问 games.html...
[CHECK] 旧token访问状态码: 302
[PASS] 验证: 登出后 session 立即失效 (302)
[PASS] TC-S-004 登出后Session立即失效 --- 通过
--- TC-S-005: 直接访问游戏子页面绕过认证 ---
[STEP] 不带 Cookie 访问 /snake.html...
→ 状态码: 302, Location: '/login.html'
[PASS] /snake.html: 正确重定向到 login.html
[STEP] 不带 Cookie 访问 /minesweeper.html...
→ 状态码: 302, Location: '/login.html'
[PASS] /minesweeper.html: 正确重定向到 login.html
[STEP] 不带 Cookie 访问 /gomoku.html...
→ 状态码: 302, Location: '/login.html'
[PASS] /gomoku.html: 正确重定向到 login.html
[STEP] 不带 Cookie 访问 /leaderboard.html...
→ 状态码: 302, Location: '/login.html'
[PASS] /leaderboard.html: 正确重定向到 login.html
[STEP] 不带 Cookie 访问 /players.html...
→ 状态码: 302, Location: '/login.html'
[PASS] /players.html: 正确重定向到 login.html
[STEP] 不带 Cookie 访问 /profile.html...
→ 状态码: 302, Location: '/login.html'
[PASS] /profile.html: 正确重定向到 login.html
[PASS] snake.html 被认证中间件保护
[PASS] minesweeper.html 被认证中间件保护
[PASS] gomoku.html 被认证中间件保护
[PASS] TC-S-005 直接访问游戏子页面绕过认证 --- 通过
--- TC-S-006: 直接访问API绕过认证 ---
[STEP] 不带 Cookie GET /api/players...
→ 状态码: 200
[FIND] /api/players 未认证即可访问 (状态码200)
[FIND] 这是预期行为: API路由不经过 AuthCheckMiddleware
[STEP] 不带 Cookie GET /api/auth/check...
→ 状态码: 200
[INFO] /api/auth/check 正常响应
[CHECK] check返回 status=error (未登录)
[STEP] 不带 Cookie GET /api/leaderboard...
→ 状态码: 200
[CHECK] /api/auth/check 响应: {'status': 'error', 'message': '未登录'}
[PASS] TC-S-006 直接访问API绕过认证 --- 通过
[NOTE] /api/players 不经过认证中间件(API路径不走IsFileHandler),
如果需要保护此端点,需在 Handler 中自行验证 session。
--- TC-S-007: 修改他人Session用户名 ---
[STEP] 检查 /api/user/update-username 端点...
[CHECK] update-username 状态码: 200
[CHECK] 响应: {'status': 'error', 'message': '请先登录'}
[PASS] API 拒绝了无效的 current_username
[PASS] TC-S-007 修改他人Session用户名 --- 通过(自动化部分)
[MANUAL] 完整测试需准备两个不同账号:
1. 用账号A登录,获取session
2. 用账号A的session尝试修改用户名为B的现有用户名
3. 验证 /api/user/update-username 检查 current_username 匹配
--- 6.1 认证与授权 结果: 7 通过, 0 失败 ---
[MANUAL] TC-S-007 完整验证: 需准备两个不同测试账号
[MANUAL] TC-S-003 完整验证: 需实际等待1小时后刷新页面
TC-S-007

测试通过


3.7 性能测试
页面加载
API接口响应
渲染性能
内存与资源
并发与极限
3.7.1 页面加载测试

TC-P-001: 首次加载时间

浏览器查看:

TC-P-002: 页面资源数量验证
测试标准: 仅games.html一个文档请求 + 2个API请求(check+players);无外部资源依赖

TC-P-003: 渲染阻塞资源

TC-P-004: 首次内容绘制(FCP)


3.7.2 API接口响应测试

TC-P-005: /api/auth/check 响应时间测试
前置条件:已经登录





TC-P-006: /api/players 响应时间测试


源代码:
cpp
void HandlePlayers(const HttpRequest &req, HttpResponse *rsp)
{
std::string accept = req.GetHeader("Accept");
if (accept.find("text/html") != std::string::npos)
{
rsp->SetRedirect("/players.html");
return;
}
// std::string cookie = req.GetHeader("Cookie");
// if(cookie.find("GAME_SESSION") == std::string::npos)
// {
// rsp->SetRedirect("/login.html");
// return;
// }
// 在锁内按 player_id 聚合,锁外拼 JSON
std::map<std::string, std::map<std::string, int>> player_best; // player_id -> (game -> best_score)
{
std::lock_guard<std::mutex> lock(g_leaderboard_mutex);
for (std::map<std::string, std::vector<ScoreEntry>>::iterator it = g_leaderboard.begin();
it != g_leaderboard.end(); ++it)
{
const std::string &game = it->first;
const std::vector<ScoreEntry> &scores = it->second;
for (size_t i = 0; i < scores.size(); i++)
{
const auto &entry = scores[i];
std::map<std::string, int> &games = player_best[entry.player_id];
if (games.find(game) == games.end() || entry.score > games[game])
games[game] = entry.score;
}
}
}
// 用 g_player_latest_name 作为显示名(实时,包括改名同步后的结果)
std::string json = "{\"status\":\"ok\",\"players\":[";
bool first_player = true;
for (std::map<std::string, std::map<std::string, int>>::iterator pit = player_best.begin();
pit != player_best.end(); ++pit)
{
std::string display_name;
{
std::lock_guard<std::mutex> lock(g_leaderboard_mutex);
auto it = g_player_latest_name.find(pit->first);
if (it != g_player_latest_name.end())
display_name = it->second;
}
if (display_name.empty())
continue;
if (!first_player)
json += ",";
first_player = false;
json += "{\"name\":\"" + EscapeJson(display_name) + "\",\"player_id\":\"" + EscapeJson(pit->first) + "\",\"games\":[";
bool first_game = true;
for (std::map<std::string, int>::iterator git = pit->second.begin();
git != pit->second.end(); ++git)
{
if (!first_game)
json += ",";
first_game = false;
json += "{\"id\":\"" + EscapeJson(git->first) + "\",";
json += "\"name\":\"" + EscapeJson(GetGameDisplayName(git->first)) + "\",";
json += "\"score\":" + std::to_string(git->second) + "}";
}
json += "]}";
}
json += "]}";
rsp->SetContent(json, "application/json; charset=utf-8");
}
性能问题分析:
- 全局互斥锁 + 高复杂度循环
后端代码代码在进入**HandlePlayers** 后,立刻使用**std::lock_guard<std::mutex>** 锁住了全局数据 g_leaderboard_mutex 。在锁内部,执行了两层嵌套循环 ,遍历所有游戏和所有玩家条目,并伴随着频繁的 std::map 插入与内存拷贝。
std::mutex 是互斥锁 (同一时间只能有一个线程进入)。当 JMeter 的 100 次请求连续轰炸时,1 个线程在锁内做耗时的循环拷贝,其余 99 个线程只能在外面死锁排队 。排在队尾的线程,由于等待时间累积,直接飙出了 7019ms 的长尾延迟。
- 锁内二次频繁加锁(上下文切换开销)
在拼接 JSON 的循环中,代码针对每一个玩家,都重新加锁去**g_player_latest_name** 里查找一次显示名。 如果有 N 个玩家,这段代码就会在执行过程中频繁加锁、解锁 N 次。在高并发网络请求下,这会引发剧烈的 CPU 上下文切换,严重拖慢整体吞吐量(导致吞吐量卡在 12.6/sec 无法提升)。
- 频繁的文件 I/O 操作
数据目前直接写入和读取文件,未引入数据库或缓存机制。
磁盘/文件 I/O 的速度比内存慢数万倍。如果每次收到 HTTP 请求都去实时读取和解析一次磁盘文件,或者在另一个线程写文件时占用了全局锁,就会导致网络线程被瞬间"定格"。
性能改善方案:
方案 1:空间换时间------全局字符串缓存法
具体实现:
- 在全局定义一个已经拼接好的最终 JSON 字符串和一把轻量锁:
cpp
std::string g_players_json_cache; // 专门存放渲染好的全局 JSON
std::mutex g_json_cache_mutex; // 极小的轻量锁
-
异步更新: 只有当有新玩家注册、提交新分数或改名时(写操作),后端才在后台重新拼接一次
g_players_json_cache。 -
极速读取:
HandlePlayers接口直接简化为:
cpp
void HandlePlayers(const HttpRequest &req, HttpResponse *rsp) {
// ... 略过 accept 校验 ...
std::string local_json;
{
std::lock_guard<std::mutex> lock(g_json_cache_mutex);
local_json = g_players_json_cache; // 仅拷贝一个现成的字符串,耗时接近 0ms
}
rsp->SetContent(local_json, "application/json; charset=utf-8");
}
方案 2:改用 C++17 读写锁(Shared Mutex)
由于业务原因,必须在用户请求时获取最新最实时的计算数据,应该立刻升级锁机制。
具体实现:
-
将 std::mutex g_leaderboard_mutex
;替换为 C++17 的std::shared_mutex g_leaderboard_mutex。 -
在
HandlePlayers(读操作)中,使用std::shared_lock(读锁/共享锁):
cpp
// 允许多个网络线程同时进来读取,不再排队
std::shared_lock<std::shared_mutex> lock(g_leaderboard_mutex);
-
只有在写入分数或修改文件(写操作)时,才使用
std::unique_lock(写锁/独占锁)。 -
优化加锁位置: 拼接 JSON 时,不要在**
for** 循环内部反复加锁解锁。在进入大循环前,一次性把需要改名的映射关系批量取出来。
TC-P-007: 5秒轮询压力验证 (Postman模拟)



TC-P-007: 5秒轮询对服务端压力测试(jmeter模拟)


TC-P-008: 服务端30秒重载开销


3.7.3 渲染性能测试
TC-P-009: 游戏卡片渲染时间

TC-P-010: 玩家列表渲染(100玩家)
先通过Postman批量提交100个玩家分数
F12 → Console 执行

TC-P-011: 页面滚动流畅度

TC-P-012: 卡片hover动画流畅度


3.7.4 内存与资源测试


TC-P-017: 弱网环境


TC-P-016: 并发客户端数量极限测试


同TC-P-006: /api/players 服务器的网络层 某个阻塞操做已经把整个事件循环彻底卡死了。由于请求都在排队或者直接超时报错返回,业务层(比如创建玩家对象、加载复杂资源等真正耗内存的操作)根本没有被高频执行。服务器处于"死等"或"秒拒"状态,内存不会有任何变化。


3.7.5 并发与极限测试

TC-P-018: 大量玩家渲染后端压力

Phase 1: 批量写入测试数据

Phase 2: 测试读取性能


JMeter 成功和服务器建立起了 TCP 连接,但是在它发送了 HTTP 请求之后,你的服务器"装死"了------在规定时间内没有任何响应,或者直接单方面死板地把这个 TCP 连接给断开了(通常是由于超时)。
-
当 500 个并发客户端涌入时,操作系统内核会帮你处理完 TCP 三次握手,并将连接放入队列。所以 JMeter 显示Connect Time
: 0,说明连接成功了。 -
但是,服务器采用的是 One Thread One Loop 架构。主线程此时被某个阻塞操作(如慢 I/O、磁盘读写)卡死,它就无法及时调用
accept()从内核队列中把连接取出来处理。 -
结果就是,JMeter 把 HTTP 请求发出去了,但请求在内核缓冲区里干等,直到超时。JMeter 没收到任何字节的 HTTP 响应头,于是抛出
NoHttpResponseException。
3.8 性能测试总结


3.9 games页面测试总结

4. leaderboard页面
4.1 功能测试
1.1 页面加载
1.2 排行榜展示
1.3 自动刷新
1.4 导航链接
4.1.1 页面加载
html
===== 1.1 页面初始化与数据加载 =====
--- TC-F-001: 正常访问 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] 验证1: 当前 URL = http://175.27.137.241:8081/leaderboard.html
[PASS] 验证2: 导航栏标题正确
[PASS] 验证3: 概览卡片区域存在
[PASS] 验证4: 游戏排行榜区域存在
[PASS] 验证5: 刷新按钮存在
[PASS] 验证6: 自动刷新提示正确
[PASS] TC-F-001 正常访问 --- 全部验证通过
--- TC-F-002: 未登录访问 ---
[STEP] 清除 Cookie 和 localStorage...
[STEP] 访问 leaderboard.html (无 session)...
[PASS] 验证: 已自动跳转到 login.html (URL: http://175.27.137.241:8081/login.html)
[PASS] TC-F-002 未登录访问 --- 通过
--- TC-F-003: 概览数据加载 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] 验证1: 概览卡片数量 = 3
[PASS] 验证2: 概览卡片包含 '贪吃蛇'
[PASS] 验证3: 概览卡片包含 '扫雷'
[PASS] 验证4: 概览卡片包含 '五子棋'
[PASS] TC-F-003 概览数据加载 --- 通过
--- TC-F-004: 排行榜数据加载 ---
[PASS] 验证1: 贪吃蛇排行榜区域存在
[PASS] 验证2: 扫雷排行榜区域存在
[PASS] 验证3: 五子棋排行榜区域存在
[PASS] 验证4: 游戏名称 = ['🐍 贪吃蛇', '💣 扫雷', '⚫ 五子棋']
[PASS] TC-F-004 排行榜数据加载 --- 通过
--- TC-F-005: 数据为空 ---
[PASS] 验证1: 概览卡片显示'-'
[PASS] 验证2: 排行榜显示空状态
[PASS] TC-F-005 数据为空 --- 通过
--- 1.1 页面初始化 结果: 5 通过, 0 失败 ---
[MANUAL] TC-F-006 接口加载失败: 需模拟服务端异常或断网
TC-F-006 接口加载失败

4.1.2 排行榜展示
html
===== 1.2 排行榜显示 =====
--- TC-F-007: 贪吃蛇排行榜内容 ---
[PASS] 验证1: 表头 = ['#', '玩家', '分数']
[PASS] 验证2: 第1名显示🥇
[PASS] 验证3: 第2名显示🥈
[PASS] 验证4: 第3名显示🥉
[PASS] 验证5: 第1名分数 = 398
[PASS] 验证6: 第2名分数 = 396
[PASS] 验证7: 第3名分数 = 393
[PASS] TC-F-007 贪吃蛇排行榜内容 --- 通过
--- TC-F-008: 扫雷排行榜内容(含难度列) ---
[PASS] 验证1: 表头 = ['#', '玩家', '难度', '分数']
[PASS] 验证2: 第1名难度 = 🤔 中等
[PASS] TC-F-008 扫雷排行榜内容 --- 通过
--- TC-F-009: 五子棋排行榜内容 ---
[PASS] 验证1: 表头 = ['#', '玩家', '分数']
[PASS] 验证2: 第1名显示🥇
[PASS] TC-F-009 五子棋排行榜内容 --- 通过
--- TC-F-010: 单个游戏排行榜为空 ---
[INFO] 所有游戏都有数据,跳过空状态验证
--- TC-F-012: 排行榜最多显示20条记录 ---
[PASS] section-gomoku 排行榜行数 = 1 (≤20)
[PASS] section-minesweeper 排行榜行数 = 1 (≤20)
[PASS] section-snake 排行榜行数 = 20 (≤20)
[PASS] TC-F-012 排行榜最多显示20条记录 --- 通过
--- TC-F-013: 排行榜按分数降序排列 ---
[INFO] section-gomoku 分数不足2条,跳过排序验证
[INFO] section-minesweeper 分数不足2条,跳过排序验证
[PASS] section-snake 分数降序排列正确 (前3: [398, 396, 393])
[PASS] TC-F-013 排行榜按分数降序排列 --- 通过
--- 1.2 排行榜显示 结果: 6 通过, 0 失败 ---
[MANUAL] TC-F-011 加载失败: 需模拟服务端异常
[MANUAL] TC-F-014~016 XSS/特殊字符: 需排行榜有对应数据



4.1.3 自动刷新
html
===== 1.3 自动刷新机制 =====
--- TC-F-012: 每10秒自动刷新 ---
[PASS] 验证1: 源码含 setInterval(loadAll, 10000)
[PASS] 验证2: loadAll 函数已定义
[PASS] 验证3: 自动刷新提示 = '⏰ 每10秒自动刷新'
[PASS] TC-F-012 每10秒自动刷新 --- 通过
--- TC-F-017: 自动刷新期间数据变化 ---
[STEP] 等待 12 秒 (一个刷新周期)...
[PASS] fetch 在12秒内被调用 14 次
[PASS] TC-F-017 自动刷新期间数据变化 --- 通过
--- TC-F-020: 自动刷新频次正确 ---
[STEP] 等待 22 秒,监控 fetch 调用频率...
[CHECK] 22秒内 fetch 总调用次数: 14
[CHECK] 过滤后批次数量: 2 (每批次约4次fetch)
[CHECK] 批次间隔1: 10000ms
[PASS] 批次平均间隔: 10000ms (期望~10000ms)
[PASS] TC-F-020 自动刷新频次正确 --- 通过
--- 1.3 自动刷新机制 结果: 3 通过, 0 失败 ---
[MANUAL] TC-F-018 数据变化: 需另一玩家提交新分数验证
[MANUAL] TC-F-019 定时器不泄漏: 需保持页面30分钟以上验证内存
[MANUAL] TC-F-021 网络中断: 需手动断网验证
[MANUAL] TC-F-022 页面离开: 需手动切换页面验证
4.1.4 导航链接

html
==== 1.6 导航链接 =====
--- TC-F-014/027: 返回游戏大厅链接 ---
[PASS] 验证1: 链接存在且文字正确
[PASS] 验证2: href = http://175.27.137.241:8081/games.html
[STEP] 点击返回游戏大厅链接...
[PASS] 验证3: 已跳转到 http://175.27.137.241:8081/games.html
[PASS] TC-F-014/027 返回游戏大厅链接 --- 通过
--- TC-F-015/028: 玩家列表链接 ---
[PASS] 验证1: 链接存在且文字正确
[PASS] 验证2: href = http://175.27.137.241:8081/players.html
[STEP] 点击玩家列表链接...
[PASS] 验证3: 已跳转到 http://175.27.137.241:8081/players.html
[PASS] TC-F-015/028 玩家列表链接 --- 通过
--- TC-F-030: 页面标题正确 ---
[PASS] 页面 title = '🏆 排行榜 - 经典小游戏'
[PASS] TC-F-030 页面标题正确 --- 通过
--- 1.6 导航链接 结果: 3 通过, 0 失败 ---
[MANUAL] TC-F-029 hover样式: 需肉眼观察CSS动画
4.2 功能测试结果


4.3 安全测试
4.1 认证
4.2 XSS防护
4.3 服务端安全
4.3.1 认证
html
--- TC-S-001: 未登录拦截 ---
[STEP] Selenium: 清除所有Cookie和localStorage...
[DEBUG] 清除后Cookie: []
[STEP] 访问 /leaderboard.html (无Cookie)...
[DEBUG] 当前URL: http://175.27.137.241:8081/login.html
[PASS] 验证1(Selenium): 已重定向到 login.html
[STEP] requests: 不带Cookie请求 /leaderboard.html...
[DEBUG] 响应状态码: 302
[DEBUG] 响应头Location: /login.html
[PASS] 验证2(requests): 302重定向到 login.html
[STEP] requests: 不带Cookie请求 /api/leaderboard...
[DEBUG] API响应状态码: 200
[INFO] /api/leaderboard 不受认证保护 (API路径公开)
[PASS] TC-S-001 未登录拦截 --- 通过
--- TC-S-002: 伪造Cookie ---
[STEP] 伪造的GAME_SESSION: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[STEP] Selenium: 先清除状态...
[DEBUG] 设置的Cookie: [{'domain': '175.27.137.241', 'httpOnly': False, 'name': 'GAME_SESSION', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'}]
[STEP] 访问 /leaderboard.html (伪造Cookie)...
[DEBUG] 当前URL: http://175.27.137.241:8081/login.html
[PASS] 验证1(Selenium): 伪造Cookie被拦截,重定向到 login.html
[STEP] requests: 带伪造Cookie请求 /leaderboard.html...
[DEBUG] 响应状态码: 302
[PASS] 验证2(requests): 302重定向到 login.html
[PASS] 验证3-1: 伪造token 'invalid_token_string...' 被拦截
[PASS] 验证3-2: 伪造token '00000000000000000000...' 被拦截
[PASS] 验证3-3: 伪造token 'ffffffffffffffffffff...' 被拦截
[PASS] 验证3-4: 伪造token '...' 被拦截
[PASS] 验证3-5: 伪造token 'xxxxxxxxxxxxxxxxxxxx...' 被拦截
[PASS] TC-S-002 伪造Cookie --- 通过
--- TC-S-003: 过期Session ---
[STEP] 测试过期/无效token...
[STEP] 登录获取有效token...
[SETUP] API 登录成功, session cookies: {'GAME_SESSION': 'c7860d201211ca8131c373b0801167a1308dca42e7639533af0249c4e91773b6'}
[DEBUG] 有效token: c7860d201211ca8131c3...
[PASS] 验证1: 有效token可以正常访问
[STEP] 登出后验证token失效...
[DEBUG] 登出后响应状态码: 302
[PASS] 验证2: 登出后token已失效,返回302
[INFO] Session超时机制验证:
[INFO] - SESSION_TIMEOUT = 3600秒 (1小时)
[INFO] - ValidateSession检查: now - last_active > SESSION_TIMEOUT
[INFO] - 超时后session自动删除
[INFO] - 完整超时测试需要等待1小时,建议手动验证
[PASS] TC-S-003 过期Session --- 通过
--- 6.1 认证安全 结果: 3 通过, 0 失败 ---
[INFO] TC-S-003 完整超时测试: 需手动等待1小时验证session过期
4.3.2 XSS防护
html
===== 6.2 XSS防护 =====
--- TC-S-004: 玩家名转义 ---
[STEP] 测试XSS payload 1: <script>alert(1)</script>...
[DEBUG] 提交响应: {'status': 'ok', 'message': '分数已记录'}
[STEP] 测试XSS payload 2: <script>alert('XSS')</script>...
[DEBUG] 提交响应: {'status': 'ok', 'message': '分数已记录'}
[STEP] 测试XSS payload 3: <img src=x onerror=alert(1)>...
[DEBUG] 提交响应: {'status': 'ok', 'message': '分数已记录'}
[STEP] 测试XSS payload 4: <svg onload=alert(1)>...
[DEBUG] 提交响应: {'status': 'ok', 'message': '分数已记录'}
[STEP] 测试XSS payload 5: <body onload=alert(1)>...
[DEBUG] 提交响应: {'status': 'ok', 'message': '分数已记录'}
[STEP] 测试XSS payload 6: javascript:alert(1)...
[DEBUG] 提交响应: {'status': 'ok', 'message': '分数已记录'}
[STEP] 测试XSS payload 7: <iframe src=javascript:alert(1)>...
[DEBUG] 提交响应: {'status': 'ok', 'message': '分数已记录'}
[STEP] 访问排行榜页面...
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[STEP] 检查是否有alert弹窗...
[PASS] 验证1: 没有alert弹窗
[STEP] 检查页面内容转义...
[PASS] 验证3: 所有HTML标签都被正确转义
[PASS] 验证4: escHtml函数存在
[PASS] escHtml('<script>alert(1)</script>') → '<script>alert(1)</script>'
[PASS] escHtml('<img src=x>') → '<img src=x>'
[PASS] escHtml('normal text') → 'normal text'
[PASS] escHtml('a&b') → 'a&b'
[PASS] escHtml('a"b') → 'a"b'
[PASS] 验证5: escHtml函数转义正确
[PASS] TC-S-004 玩家名转义 --- 通过
--- TC-S-005: innerHTML安全 ---
[STEP] 检查escHtml函数实现...
[DEBUG] escHtml实现: function escHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
[PASS] 验证1: escHtml使用安全的转义方式
[STEP] 检查loadGameLeaderboard函数...
[DEBUG] loadGameLeaderboard实现片段: async function loadGameLeaderboard(gameId) {
const resp = await fetch('/api/leaderboard?game=' + gameId, { credentials: 'same-origin' });
const data = await resp.json();
if (data.status !== 'ok') throw new Error('Failed');
if (!data.scores || data.scores.length === 0) {
return `<div class="empty-state"><div class="icon">📭</div><p>暂无玩家记录</p></div>`;
}
const isMinesweeper = (gameId === 'minesweeper');
const top = data.scores.slice(0, 20);
let rows = '';
top.forEach((s, i) =...
[PASS] 验证2: loadGameLeaderboard使用escHtml
[STEP] 检查loadSummary函数...
[DEBUG] loadSummary实现片段: async function loadSummary() {
const resp = await fetch('/api/leaderboard', { credentials: 'same-origin' });
const data = await resp.json();
if (data.status !== 'ok') throw new Error('Failed to load');
let html = '';
data.games.forEach(g => {
html += `
<div class="summary-card">
<div class="card-icon">${g.name.match(/^./u)[0]}</div>
<div class="card-title">${g.name.replace(/^.\s*/u, '')}</div>
<div class="card-value">${g.top_score > 0 ? g.top_score : ...
[PASS] 验证3: loadSummary使用escHtml
[STEP] 检查innerHTML使用安全性...
[DEBUG] 找到 6 个innerHTML赋值点
[PASS] innerHTML赋值点1安全: >`;
});
document.getElementById('summaryGrid').innerHTML = html;
}
async fu...
[PASS] innerHTML赋值点2安全: cument.getElementById('gameSections');
container.innerHTML = '<div class="load...
[PASS] innerHTML赋值点3安全: h (e) {
document.getElementById('summaryGrid').innerHTML =
`<div class...
[PASS] innerHTML赋值点4安全: ></div></div></div>
</div>`;
}
container.innerHTML = sectionsHtml;
...
[PASS] innerHTML赋值点5安全: document.getElementById('content-' + game.id).innerHTML = html;
} catch...
[PASS] innerHTML赋值点6安全: document.getElementById('content-' + game.id).innerHTML =
`<div cla...
[STEP] 提交特殊字符验证显示...
[PASS] 提交特殊字符'HTML标签'成功
[PASS] 提交特殊字符'双引号'成功
[PASS] 提交特殊字符'单引号'成功
[PASS] 提交特殊字符'&符号'成功
[PASS] 提交特殊字符'换行符'成功
[PASS] 验证5: 特殊字符被正确处理
[PASS] TC-S-005 innerHTML安全 --- 通过
--- 6.2 XSS防护 结果: 2 通过, 0 失败 ---
4.3.3 服务端安全

html
===== 6.3 服务端安全 --- 路径穿越 =====
--- TC-S-006: 路径穿越 ---
[STEP] 测试路径穿越payload...
[PASS] payload 1 被拦截 (HTTP 404): /../../../etc/passwd
[PASS] payload 2 被拦截 (HTTP 404): /leaderboard.html/../../../etc/passwd
[PASS] payload 3 被拦截 (HTTP 404): /leaderboard.html/../../etc/passwd
[PASS] payload 4 被拦截 (HTTP 404): /leaderboard.html/../etc/passwd
[PASS] payload 5 被拦截 (HTTP 404): /../../../../../../etc/passwd
[PASS] payload 6 被拦截 (HTTP 404): /../../../../../../etc/shadow
[PASS] payload 7 被拦截 (HTTP 404): /../../../../../../etc/hosts
[PASS] payload 8 被拦截 (HTTP 404): /..%2F..%2F..%2Fetc%2Fpasswd
[PASS] payload 9 被拦截 (HTTP 404): /%2e%2e/%2e%2e/%2e%2e/etc/passwd
[PASS] payload 10 被拦截 (HTTP 404): /..%252f..%252f..%252fetc%252fpasswd
[PASS] payload 11 被拦截 (HTTP 404): /..//../..//etc/passwd
[PASS] payload 12 被拦截 (HTTP 404): //..//..//etc/passwd
[PASS] payload 13 被拦截 (HTTP 404): /./../../../etc/passwd
[PASS] payload 14 被拦截 (HTTP 404): /././../../../etc/passwd
[PASS] payload 15 被拦截 (HTTP 404): /../../../etc/hostname
[PASS] payload 16 被拦截 (HTTP 404): /../../../proc/self/cmdline
[PASS] payload 17 被拦截 (HTTP 404): /../../../proc/version
[PASS] payload 18 被拦截 (HTTP 404): /../../../source/http/game.cpp
[PASS] payload 19 被拦截 (HTTP 404): /../../../source/http/auth.hpp
[PASS] payload 20 被拦截 (HTTP 404): /../../../source/http/leaderboard_data.txt
[PASS] payload 21 被拦截 (HTTP 404): /..\..\..\windows\system32\config\sam
[PASS] payload 22 被拦截 (HTTP 404): /..\..\..\etc\passwd
[RESULT] 22/22 个payload被拦截
[STEP] 测试ValidPath边界情况...
[PASS] 边界路径1 重定向 (302→/login.html): /leaderboard.html
[PASS] 边界路径2 被拦截 (404): /leaderboard.html/
[PASS] 边界路径3 重定向 (302→/login.html): //leaderboard.html
[PASS] 边界路径4 重定向 (302→/login.html): /./leaderboard.html
[PASS] 边界路径5 被拦截 (404): /leaderboard.html/.
[PASS] 边界路径6 重定向 (302→/login.html): /leaderboard.html/..
[PASS] 边界路径7 重定向 (302→/login.html): /leaderboard.html/../
[STEP] 验证错误响应不泄露敏感信息...
[PASS] 错误页面未泄露敏感信息: /../../../etc/passwd
[PASS] 错误页面未泄露敏感信息: /nonexistent_file.html
[PASS] 错误页面未泄露敏感信息: /api/nonexistent
[PASS] TC-S-006 路径穿越 --- 通过
--- 6.3 路径穿越 结果: 1 通过, 0 失败 ---
===== 6.3 服务端安全 --- API参数注入 =====
--- TC-S-007: API参数注入 ---
[STEP] 测试 /api/leaderboard 的 game 参数注入...
[INFO] payload 1 返回 1 条数据: ../../etc/passwd
[PASS] payload 2 返回空数据: ../../../etc/passwd
[PASS] payload 3 返回空数据: ../../../../etc/passwd
[PASS] payload 4 返回空数据: ../../../../../etc/passwd
[PASS] payload 5 返回空数据: ' OR '1'='1
[PASS] payload 6 返回空数据: '; DROP TABLE users; --
[PASS] payload 7 返回空数据: 1; SELECT * FROM users
[PASS] payload 8 返回空数据: ; cat /etc/passwd
[PASS] payload 9 返回空数据: | cat /etc/passwd
[PASS] payload 10 返回空数据: `cat /etc/passwd`
[PASS] payload 11 返回空数据: $(cat /etc/passwd)
[PASS] payload 12 返回空数据: <script>alert(1)</script>
[PASS] payload 13 返回空数据: javascript:alert(1)
[PASS] payload 14 返回空数据: {{7*7}}
[PASS] payload 15 返回空数据: ${7*7}
[PASS] payload 16 返回空数据:
[PASS] payload 17 返回空数据:
[PASS] payload 18 返回空数据: null
[PASS] payload 19 返回空数据: undefined
[PASS] payload 20 返回空数据: NaN
[PASS] payload 21 返回空数据: 0
[PASS] payload 22 返回空数据: -1
[PASS] payload 23 返回空数据: 999999999
[PASS] payload 24 返回空数据: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[PASS] payload 25 返回空数据: %00
[PASS] payload 26 返回空数据: %0d%0a
[PASS] payload 27 返回空数据:
[PASS] payload 28 返回空数据: snake
[PASS] payload 29 返回空数据: Snake
[PASS] payload 30 返回空数据: SNAKE
[PASS] payload 31 返回空数据: SNAKE
[PASS] payload 32 返回空数据: snake
[PASS] payload 33 返回空数据: snake
[PASS] payload 34 返回空数据: snake;
[PASS] payload 35 返回空数据: snake&
[PASS] payload 36 返回空数据: snake#
[PASS] payload 37 返回空数据: snake?
[RESULT] 36/37 个payload返回空或错误
[STEP] 测试已知game_id...
[PASS] game=snake: 82 个玩家, 82 条记录
[PASS] game=minesweeper: 1 个玩家, 1 条记录
[PASS] game=gomoku: 1 个玩家, 1 条记录
[STEP] 测试 /api/score 的参数注入...
[INFO] 注入payload 1 被接受: {'game': 'snake', 'player': '<script>alert(1)</script>', 'score': '100', 'player_id': 'test_xss'}
[INFO] 注入payload 2 被接受: {'game': 'snake', 'player': 'normal_player', 'score': '-999999', 'player_id': 'test_neg'}
[INFO] 注入payload 3 被接受: {'game': 'snake', 'player': 'normal_player', 'score': '999999999', 'player_id': 'test_big'}
[INFO] 注入payload 4 被接受: {'game': '../../etc/passwd', 'player': 'test', 'score': '100', 'player_id': 'test_game_inject'}
[INFO] 注入payload 5 被接受: {'game': 'snake', 'player': 'test', 'score': 'abc', 'player_id': 'test_nan'}
[PASS] 注入payload 6 被拒绝: 参数错误
[PASS] 注入payload 7 被拒绝: 请输入真实玩家名
[STEP] 测试 /api/players 的参数注入...
[PASS] /api/players 返回 85 个玩家
[PASS] /api/players 数据安全
[STEP] 测试请求方法限制...
[INFO] POST /api/leaderboard 返回 404
[INFO] POST /api/players 返回 404
[INFO] POST /api/auth/check 返回 404
[INFO] GET /api/score 返回 404
[INFO] GET /api/auth/login 返回 404
[INFO] GET /api/auth/register 返回 404
[PASS] TC-S-007 API参数注入 --- 通过
--- 6.3 API参数注入 结果: 1 通过, 0 失败 ---
===== 6.3 服务端安全 --- 并发安全 =====
--- TC-S-008: 锁竞争安全 ---
[STEP] 并发GET请求: 20线程 x 5请求/线程...
[RESULT] 总请求: 100
[RESULT] 成功: 100
[RESULT] 错误: 0
[RESULT] 超时: 0
[RESULT] 成功率: 100.0%
[PASS] 验证1: 并发GET请求成功率≥90%
[STEP] 并发POST请求: 提交分数...
[RESULT] POST成功: 10
[RESULT] POST错误: 0
[STEP] 验证并发后服务器仍正常...
[PASS] 验证3: 并发后服务器仍正常响应
[STEP] 混合并发: GET + POST 同时进行...
[RESULT] GET成功: 5
[RESULT] POST成功: 10
[RESULT] 错误: 0
[PASS] 验证4: 混合并发无错误
[PASS] TC-S-008 锁竞争安全 --- 通过
--- TC-S-009: 快照隔离 ---
[STEP] 验证单次请求内数据一致性...
[DEBUG] 概览数据: 3 个游戏
[INFO] snake: 概览玩家数=84, 详情玩家数=83 (可能有并发更新)
[PASS] snake: 最高分一致 (999999999)
[PASS] minesweeper: 玩家数一致 (1)
[PASS] minesweeper: 最高分一致 (81)
[PASS] gomoku: 玩家数一致 (1)
[PASS] gomoku: 最高分一致 (80)
[STEP] 验证详情数据内部一致性...
[PASS] snake: 分数降序排列正确
[PASS] snake: 排名连续正确
[PASS] snake: 所有玩家名非空
[PASS] minesweeper: 分数降序排列正确
[PASS] minesweeper: 排名连续正确
[PASS] minesweeper: 所有玩家名非空
[PASS] gomoku: 分数降序排列正确
[PASS] gomoku: 排名连续正确
[PASS] gomoku: 所有玩家名非空
[STEP] 验证并发请求间数据不混杂...
[DEBUG] 成功获取: 10/10
[PASS] 验证3: 并发请求数据结构一致
[STEP] 验证快照机制...
[PASS] 验证4: 连续两次请求数据完全一致(快照机制正常)
[PASS] TC-S-009 快照隔离 --- 通过
--- 6.3 并发安全 结果: 2 通过, 0 失败 ---
[INFO] TC-S-008 完整压力测试: 建议使用JMeter进行100并发测试
[INFO] TC-S-009 深度一致性测试: 需要长时间监控数据变化
关闭 WebDriver...
============================================================
leaderboard.html 安全测试结果汇总
─────────────────────────
通过: 5 / 5
============================================================
4.4 安全测试结果


4.5 性能测试

4.5.1 页面加载
TC-P-001 页面加载时间

TC-P-002 资源数量
|------|------------------------|
| 测试目标 | 1个HTML + 4个API请求;无外部资源 |
为什么请求了两次?

4.5.2 API响应

TC-P-003 /api/leaderboard 响应时间




......
TC-P-004 10秒轮询压力测试
前置条件: 准备好一百个用户 因为后端设置了非活跃超时连接关闭10s 当大量请求到来时候可能会引起 Connection reset
原因:JMeter 默认开启了 Keep-Alive (保持长连接)。如果 JMeter 以为连接还能用,正准备发数据,但服务器的 Web 服务器刚好因为超时(Keep-Alive timeout)10s 把这个空闲连接给关闭了,就会产生这个时间差碰撞,引发 Connection reset。
解决方案:在线程组下加一个 HTTP信息头管理器添加一个头部字段为 Connection:close



4.5.3 渲染性能

TC-P-005 排行榜渲染性能

4.6 性能测试结果
(部分)

4.7 易用性测试 (略)

4.8 兼容性测试 (略)

4.0 界面测试(略)

5. players 页面
5.1 功能测试
1.1 页面初始化与登录验证
1.2 统计数据展示
1.3 玩家列表展示
1.4 自动刷新机制
1.5 空状态与错误处理
1.6 导航链接
5.1.1 页面初始化与登录验证
html
===== 1.1 页面初始化与登录验证 =====
--- TC-F-001: 已登录用户正常访问 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] 验证1: 当前 URL = http://175.27.137.241:8081/players.html
[PASS] 验证2: 导航栏标题正确
[PASS] 验证3: 统计卡片区域存在
[PASS] 验证4: 玩家列表区域存在
[PASS] 验证5: 刷新按钮存在
[PASS] 验证6: 自动刷新提示正确
[PASS] 验证7: 页面无错误状态
[PASS] TC-F-001 已登录用户正常访问 --- 全部验证通过
--- TC-F-002: 未登录用户访问自动跳转 ---
[STEP] 清除 Cookie 和 localStorage...
[STEP] 访问 players.html (无 session)...
[PASS] 验证: 已自动跳转到 login.html (URL: http://175.27.137.241:8081/login.html)
[PASS] TC-F-002 未登录用户访问自动跳转 --- 通过
--- TC-F-005: 页面Title正确 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] 页面 title = '👥 玩家列表 - 经典小游戏'
[PASS] TC-F-005 页面Title正确 --- 通过
--- 1.1 页面初始化与登录验证 结果: 3 通过, 0 失败 ---
[MANUAL] TC-F-003 Session过期: 需等待1小时或模拟过期
[MANUAL] TC-F-004 网络异常: 需模拟网络断连
TC-F-004 网络异常: 需模拟网络断连
5.1.2 统计数据展示
html
===== 1.2 统计数据展示 =====
--- TC-F-006: 统计卡片正常显示 ---
[PASS] 验证1: 统计卡片数量 = 4
[PASS] 验证2: 卡片标签 = '👤 总玩家数'
[PASS] 验证3: 卡片标签 = '📊 总分数'
[PASS] 验证4: 卡片标签 = '🏆 最高分'
[PASS] 验证5: 卡片标签 = '🎮 参与次数'
[PASS] TC-F-006 统计卡片正常显示 --- 通过
--- TC-F-007: 总玩家数统计正确 ---
[PASS] 验证1: 标签 = '👤 总玩家数'
[PASS] 验证2: 总玩家数 = 17
[PASS] TC-F-007 总玩家数统计正确 --- 通过
--- TC-F-011: 玩家数量显示 ---
[CHECK] 玩家数量信息: '👤 共 17 位玩家'
[PASS] 验证1: 包含👤图标
[PASS] 验证2: 包含'位玩家'
[PASS] 验证3: 包含数字
[PASS] TC-F-011 玩家数量显示 --- 通过
--- TC-F-012: 统计数据为空 ---
[INFO] 当前有 17 个玩家,跳过空数据验证
--- 1.2 统计数据展示 结果: 4 通过, 0 失败 ---
[MANUAL] TC-F-008~010 具体数值验证: 需要特定数据环境
TC-F-008~010 具体数值验证

TC-F-012: 统计数据为空

5.1.3 玩家列表展示

html
===== 1.3 玩家列表展示 =====
--- TC-F-013: 玩家列表正常渲染 ---
[PASS] 验证1: 玩家区域数量 = 5
[PASS] 验证2: 玩家名称 = '07b4k4fsc8m'
[PASS] 验证3: 玩家名称 = '7fxtqu'
[PASS] 验证4: 玩家名称 = '<script>alert(1)</script>'
[PASS] 验证5: 玩家总分 = '🏆 52'
[PASS] 验证6: 玩家总分 = '🏆 267'
[PASS] 验证7: 玩家总分 = '🏆 353'
[PASS] 验证8: 玩家头像区域存在
[PASS] 验证9: 玩家头像区域存在
[PASS] 验证10: 玩家头像区域存在
[PASS] TC-F-013 玩家列表正常渲染 --- 通过
--- TC-F-015: 玩家头像加载失败降级 ---
[PASS] 验证1: 首字符头像存在
[PASS] 验证2: 首字符 = '0' (数字)
[PASS] TC-F-015 玩家头像加载失败降级 --- 通过
--- TC-F-016: 玩家名称显示 ---
[PASS] 验证1: 玩家名称 = '07b4k4fsc8m'
[PASS] 验证2: 字重 = 600
[PASS] TC-F-016 玩家名称显示 --- 通过
--- TC-F-017: 玩家总分计算 ---
[PASS] 验证1: 总分 = '🏆 52'
[PASS] 验证2: 字重 = 700
[PASS] TC-F-017 玩家总分计算 --- 通过
--- TC-F-019: 展开/折叠玩家详情 ---
[CHECK] 初始状态: expanded=False
[STEP] 点击第一个玩家区域...
[PASS] 验证1: 状态已切换为 expanded=True
[PASS] 验证2: 再次点击恢复原状态
[PASS] TC-F-019 展开/折叠玩家详情 --- 通过
--- TC-F-020: 游戏分数卡片显示 ---
[STEP] 展开第一个玩家区域...
[PASS] 验证1: 游戏分数卡片数量 = 3
[PASS] 验证2: 游戏名称 = '🐍 贪吃蛇'
[PASS] 验证3: 游戏名称 = '💣 扫雷'
[PASS] 验证4: 游戏名称 = '⚫ 五子棋'
[PASS] 验证5: 分数 = '52'
[PASS] 验证6: 分数 = '-'
[PASS] 验证7: 分数 = '-'
[PASS] TC-F-020 游戏分数卡片显示 --- 通过
--- TC-F-022: 头像缓存刷新 ---
[PASS] 验证: 头像URL包含时间戳参数
[PASS] TC-F-022 头像缓存刷新 --- 通过
--- 1.3 玩家列表展示 结果: 7 通过, 0 失败 ---
[MANUAL] TC-F-014 头像显示: 需要玩家有自定义头像
[MANUAL] TC-F-021 分数颜色: 需肉眼观察CSS样式


5.1.4 自动刷新机制
html
===== 1.4 自动刷新机制 =====
--- TC-F-023: 每10秒自动刷新 ---
[PASS] 验证1: 源码含 setInterval(loadPlayers, 10000)
[PASS] 验证2: loadPlayers 函数已定义
[PASS] 验证3: 自动刷新提示 = '⏰ 每10秒自动刷新'
[PASS] TC-F-023 每10秒自动刷新 --- 通过
--- TC-F-026: loadPlayers频次合理 ---
[STEP] 等待 22 秒,监控 fetch 调用频率...
[CHECK] 22秒内 fetch 总调用次数: 2
[CHECK] 过滤后批次数量: 2
[CHECK] 批次间隔1: 10000ms
[PASS] 批次平均间隔: 10000ms (期望~10000ms)
[PASS] TC-F-026 loadPlayers频次合理 --- 通过
--- TC-F-028: 手动刷新功能 ---
[CHECK] 刷新前数据: '👤 共 17 位玩家'
[STEP] 点击刷新按钮...
[PASS] 验证1: 刷新按钮存在且可点击
[PASS] 验证2: 刷新后数据: '👤 共 17 位玩家'
[PASS] 验证3: loadPlayers 被调用 1 次
[PASS] TC-F-028 手动刷新功能 --- 通过
--- 1.4 自动刷新机制 结果: 3 通过, 0 失败 ---
[MANUAL] TC-F-024 数据变化: 需另一玩家提交新分数验证
[MANUAL] TC-F-025 定时器不泄漏: 需保持页面30分钟以上验证内存
[MANUAL] TC-F-027 网络中断: 需手动断网验证
5.1.5 空状态与错误处理
html
===== 1.5 空状态与错误处理 =====
--- TC-F-029: 无玩家数据空状态 ---
[INFO] 当前有 17 个玩家,跳过空状态验证
--- 1.5 空状态与错误处理 结果: 1 通过, 0 失败 ---
[MANUAL] TC-F-030 API返回错误: 需模拟API失败
[MANUAL] TC-F-031 API返回非JSON: 需模拟非JSON响应
[MANUAL] TC-F-032 API网络异常: 需模拟网络断连
[MANUAL] TC-F-033 API数据格式错误: 需模拟格式错误

5.1.6 导航链接

html
===== 1.6 导航链接 =====
--- TC-F-034: 游戏大厅链接 ---
[PASS] 验证1: 链接存在且文字正确
[PASS] 验证2: href = http://175.27.137.241:8081/games.html
[STEP] 点击返回游戏大厅链接...
[PASS] 验证3: 已跳转到 http://175.27.137.241:8081/games.html
[PASS] TC-F-034 游戏大厅链接 --- 通过
--- TC-F-035: 排行榜链接 ---
[PASS] 验证1: 链接存在且文字正确
[PASS] 验证2: href = http://175.27.137.241:8081/leaderboard.html
[STEP] 点击排行榜链接...
[PASS] 验证3: 已跳转到 http://175.27.137.241:8081/leaderboard.html
[PASS] TC-F-035 排行榜链接 --- 通过
--- 1.6 导航链接 结果: 2 通过, 0 失败 ---
[MANUAL] TC-F-036 hover样式: 需肉眼观察CSS动画

5.2 安全测试
5.1 认证与授权
5.2 XSS防护
5.3 CSRF防护
5.4 信息泄露
5.5 前端逻辑安全
5.6 服务端安全
5.2.1 认证与授权

html
===== 6.1 认证与授权 =====
--- TC-S-001: 未登录直接访问players.html ---
[STEP] Selenium: 清除所有Cookie和localStorage...
[DEBUG] 清除后Cookie: []
[STEP] 访问 /players.html (无Cookie)...
[DEBUG] 当前URL: http://175.27.137.241:8081/login.html
[PASS] 验证1(Selenium): 已重定向到 login.html
[STEP] requests: 不带Cookie请求 /players.html...
[DEBUG] 响应状态码: 302
[DEBUG] 响应头Location: /login.html
[PASS] 验证2(requests): 302重定向到 login.html
[PASS] TC-S-001 未登录直接访问players.html --- 通过
--- TC-S-002: 伪造Session Cookie ---
[STEP] 伪造的GAME_SESSION: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[STEP] Selenium: 先清除状态...
[DEBUG] 设置的Cookie: [{'domain': '175.27.137.241', 'httpOnly': False, 'name': 'GAME_SESSION', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'}]
[STEP] 访问 /players.html (伪造Cookie)...
[DEBUG] 当前URL: http://175.27.137.241:8081/login.html
[PASS] 验证1(Selenium): 伪造Cookie被拦截,重定向到 login.html
[STEP] requests: 带伪造Cookie请求 /players.html...
[DEBUG] 响应状态码: 302
[PASS] 验证2(requests): 302重定向到 login.html
[PASS] 验证3-1: 伪造token 'invalid_token_string...' 被拦截
[PASS] 验证3-2: 伪造token '00000000000000000000...' 被拦截
[PASS] 验证3-3: 伪造token 'ffffffffffffffffffff...' 被拦截
[PASS] 验证3-4: 伪造token '...' 被拦截
[PASS] 验证3-5: 伪造token 'xxxxxxxxxxxxxxxxxxxx...' 被拦截
[PASS] TC-S-002 伪造Session Cookie --- 通过
--- TC-S-003: 过期Session ---
[STEP] 登录获取有效token...
[WARN] 过期Session测试异常: 无法解析验证码: '5 Ã--- 2 = ?'
[INFO] 请确保服务器和Redis正常运行
[PASS] TC-S-003 过期Session --- 通过
--- TC-S-004: 登出后Session立即失效 ---
[STEP] 登录并访问players.html...
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] 验证1: 登录后正常访问players.html
[STEP] 通过API登出...
[DEBUG] 登出响应: 200
[STEP] 登出后访问players.html...
[DEBUG] 当前URL: http://175.27.137.241:8081/login.html
[PASS] 验证3: 登出后访问被拦截,重定向到login.html
[PASS] TC-S-004 登出后Session立即失效 --- 通过
--- TC-S-005: 直接访问API绕过认证 ---
[验证1] 检查受保护的 GET API...
[FAIL] 安全漏洞! GET /api/players 未受认证保护!
[FAIL] 安全漏洞! GET /api/user/profile 未受认证保护!
[INFO] GET /api/user/avatar 返回404 (不存在)
[FAIL] 安全漏洞! GET /api/auth/check 未受认证保护!
[FAIL] 安全漏洞! GET /api/leaderboard 未受认证保护!
[验证2] 检查受保护的 POST API...
[FAIL] 安全漏洞! POST /api/auth/logout 未受认证保护!
[FAIL] 安全漏洞! POST /api/user/update-username 未受认证保护!
[INFO] POST /api/user/update-avatar 返回404 (不存在)
[FAIL] 安全漏洞! POST /api/score 未受认证保护!
[FAIL] 安全漏洞! POST /api/sync-name 未受认证保护!
[验证3] 检查公开API端点(应该可以不登录访问)...
[检查] 公开 GET API...
[INFO] GET /api/auth/login 响应: 404
[PASS] GET /api/auth/captcha?id=test 公开访问正常
[PASS] GET /api/auth/check-user 公开访问正常
[检查] 公开 POST API...
[PASS] POST /api/auth/register 公开访问正常
[FAIL] TC-S-005: 安全漏洞: 以下API端点未受认证保护: ['GET /api/players', 'GET /api/user/profile', 'GET /api/auth/check', 'GET /api/leaderboard', 'POST /api/auth/logout', 'POST /api/user/update-username', 'POST /api/score', 'POST /api/sync-name']
--- 6.1 认证与授权 结果: 4 通过, 1 失败 ---
[INFO] TC-S-003 完整超时测试: 需手动等待1小时验证session过期
[FAIL] 6.1 认证与授权 测试失败: 认证安全测试: 1 个用例失败
5.2.2 XSS防护

html
===== 6.2 XSS防护 =====
--- TC-S-006: 玩家名XSS向量 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[STEP] 检查escHtml函数是否存在...
[PASS] 验证1: escHtml函数存在
[STEP] 测试escHtml函数转义效果...
[PASS] escHtml('<script>alert(1)</script>...') → '<script>alert(1)</scr...'
[PASS] escHtml('<img src=x onerror=alert(1)>...') → '<img src=x onerror=alert(1)...'
[PASS] escHtml('<svg onload=alert(1)>...') → '<svg onload=alert(1)>...'
[PASS] escHtml('normal text...') → 'normal text...'
[PASS] escHtml('a&b...') → 'a&b...'
[PASS] escHtml('<b>bold</b>...') → '<b>bold</b>...'
[PASS] 验证2: escHtml函数转义正确
[STEP] 检查是否有alert弹窗...
[PASS] 验证3: 没有alert弹窗
[STEP] 检查页面中玩家名转义...
[PASS] TC-S-006 玩家名XSS向量 --- 通过
--- TC-S-007: 特殊字符玩家名 ---
[STEP] 测试escHtml对特殊字符的转义...
[PASS] escHtml('a&b') → 'a&b'
[PASS] escHtml('a<b') → 'a<b'
[PASS] escHtml('a>b') → 'a>b'
[PASS] escHtml('a"b') → 'a"b'
[PASS] escHtml('a'b') → 'a'b'
[PASS] escHtml('a/b') → 'a/b'
[PASS] 验证1: 特殊字符转义正确
[STEP] 检查页面HTML结构完整性...
[PASS] 元素 container 存在
[PASS] 元素 navBar 存在
[PASS] 元素 playerList 存在
[PASS] 元素 refreshBtn 存在
[PASS] 元素 statsRow 存在
[PASS] 验证2: 页面HTML结构完整
[PASS] TC-S-007 特殊字符玩家名 --- 通过
--- TC-S-008: 玩家名含emoji ---
[STEP] 测试escHtml对emoji的处理...
[PASS] escHtml('🎮游戏') → '🎮游戏'
[PASS] escHtml('🔥火') → '🔥火'
[PASS] escHtml('💣炸弹') → '💣炸弹'
[PASS] escHtml('玩家🎮🔥💣') → '玩家🎮🔥💣'
[PASS] 验证1: emoji处理正确
[STEP] 检查页面中emoji显示...
[PASS] 验证2: 页面中发现emoji: ['🐍', '💣', '⚫', '👥', '🏆', '📊', '🎮']
[PASS] TC-S-008 玩家名含emoji --- 通过
--- TC-S-009: 头像URL注入 ---
[STEP] 检查loadPlayers函数中URL编码...
[PASS] 验证1: loadPlayers使用encodeURIComponent
[STEP] 检查页面中头像URL格式...
[DEBUG] 头像URL 1: http://175.27.137.241:8081/api/user/avatar?player_id=p_1777645482649_cupxpdd8g&t...
[PASS] 验证2: 检查了 1 个头像URL格式
[STEP] 测试encodeURIComponent编码效果...
[PASS] encodeURIComponent('normal_id') → 'normal_id'
[PASS] encodeURIComponent('id with space') → 'id%20with%20space'
[PASS] encodeURIComponent('id¶m=value') → 'id%26param%3Dvalue'
[PASS] encodeURIComponent('id#fragment') → 'id%23fragment'
[PASS] encodeURIComponent('id?query=1') → 'id%3Fquery%3D1'
[PASS] 验证3: encodeURIComponent编码正确
[PASS] TC-S-009 头像URL注入 --- 通过
--- TC-S-010: innerHTML使用安全 ---
[STEP] 检查loadPlayers函数中innerHTML使用...
[DEBUG] 找到 5 个innerHTML赋值点
[WARN] innerHTML赋值点1可能未转义用户数据:
.id] = (gamePlayerCounts[g.id] || 0) + 1;
});
totalScore += pTotal;
});
statsEl...
[WARN] innerHTML赋值点2可能未转义用户数据:
: 0}</div><div class="lbl">🎮 参与次数</div></div>
`;
if (players.length === 0) {
listEl....
[PASS] innerHTML赋值点3安全
[PASS] innerHTML赋值点4安全
[PASS] innerHTML赋值点5安全
[WARN] 验证1: 存在可能未转义的innerHTML赋值点
[STEP] 检查statsEl.innerHTML安全性...
[PASS] 验证2: statsEl.innerHTML使用数值,安全
[STEP] 检查listEl.innerHTML安全性...
[PASS] 验证3: listEl.innerHTML使用escHtml转义
[PASS] 验证3: 未发现p.name直接使用
[PASS] TC-S-010 innerHTML使用安全 --- 通过
--- 6.2 XSS防护 结果: 5 通过, 0 失败 ---
5.2.3 CSRF防护

5.2.4 信息泄露

html
--- TC-S-013: 响应头信息泄露 ---
[STEP] requests: 检查 /players.html 响应头...
[DEBUG] 响应状态码: 302
[DEBUG] 响应头:
[PASS] 头 Server 未泄露
[PASS] 头 X-Powered-By 未泄露
[PASS] 头 X-AspNet-Version 未泄露
[PASS] 头 X-AspNetMvc-Version 未泄露
[PASS] 头 X-Runtime 未泄露
[PASS] 头 X-Version 未泄露
[PASS] 验证1: 无敏感响应头信息泄露
[STEP] requests: 检查 /api/players 响应头...
[PASS] API响应头 Server 未泄露
[PASS] API响应头 X-Powered-By 未泄露
[PASS] API响应头 X-AspNet-Version 未泄露
[PASS] API响应头 X-AspNetMvc-Version 未泄露
[PASS] API响应头 X-Runtime 未泄露
[PASS] API响应头 X-Version 未泄露
[STEP] 检查安全相关响应头...
[INFO] 安全头 Content-Security-Policy 未设置
[INFO] 安全头 X-Content-Type-Options 未设置
[INFO] 安全头 X-Frame-Options 未设置
[INFO] 安全头 X-XSS-Protection 未设置
[PASS] TC-S-013 响应头信息泄露 --- 通过
--- TC-S-014: 前端代码信息泄露 ---
[STEP] 获取页面源码并检查敏感信息...
[PASS] 验证1: 未发现敏感信息关键词
[STEP] 检查API端点路径...
[INFO] 发现API端点: /api/players (公开接口)
[INFO] 发现API端点: /api/user/avatar (公开接口)
[STEP] 检查硬编码的IP或域名...
[PASS] 验证3: 未发现硬编码IP
[PASS] TC-S-014 前端代码信息泄露 --- 通过
--- TC-S-015: 错误消息不泄露后端信息 ---
[STEP] 检查错误状态显示...
[INFO] 验证1: 页面无错误状态(正常加载)
[STEP] 检查loadPlayers函数错误处理...
[PASS] 验证2: loadPlayers有错误处理
[PASS] 验证2: 错误处理显示通用消息'加载失败'
[INFO] 验证2: 错误处理包含e.message(可能泄露错误详情)
[PASS] TC-S-015 错误消息不泄露后端信息 --- 通过
--- TC-S-016: 玩家ID不泄露 ---
[STEP] 检查页面中是否有明文显示的player_id...
[PASS] 玩家 1 名称区域无player_id: sy
[PASS] 验证1: 页面中无明文显示的player_id
[STEP] 验证player_id只在头像URL中使用...
[PASS] 头像URL 1 使用player_id参数
[STEP] 检查页面文本内容...
[PASS] 验证3: 页面文本中无UUID格式内容
[PASS] TC-S-016 玩家ID不泄露 --- 通过
--- 6.4 信息泄露 结果: 4 通过, 0 失败 ---
5.2.5 前端逻辑安全

cpp
===== 6.5 前端逻辑安全 =====
--- TC-S-017: 前端跳转可被绕过 ---
[STEP] 通过API登录获取有效Cookie...
[SETUP] API 登录成功, session cookies: {'GAME_SESSION': '0e05a49e57f14055bce906142ffe18546d0a13e3e4684f9483d8804a93d8e50c'}
[DEBUG] 有效Cookie: GAME_SESSION=0e05a49e57f14055bce9...
[STEP] 使用有效Cookie直接请求 /players.html...
[DEBUG] 响应状态码: 200
[PASS] 验证1: 有效Cookie返回200
[STEP] 检查返回的页面内容...
[WARN] 测试异常: 页面内容应包含players.html相关内容
[INFO] 请确保服务器和Redis正常运行
[PASS] TC-S-017 前端跳转可被绕过 --- 通过
--- TC-S-018: 定时器安全 ---
[STEP] 检查setInterval的使用...
[DEBUG] 找到 1 个setInterval调用
[DEBUG] setInterval 1: loadPlayers 每 10000ms
[PASS] 验证1: setInterval调用loadPlayers
[STEP] 检查定时器间隔...
[PASS] 验证2: 定时器间隔为 10000ms (10秒)
[STEP] 验证定时器仅用于展示...
[PASS] 验证3: 定时器只用于获取数据
[PASS] 验证3: 定时器不发送POST请求
[PASS] 验证3: 定时器不传输敏感数据
[STEP] 检查自动刷新提示...
[PASS] 验证4: 自动刷新提示正确: '⏰ 每10秒自动刷新'
[PASS] TC-S-018 定时器安全 --- 通过
--- TC-S-019: 头像接口参数验证 ---
[STEP] 测试正常player_id...
[DEBUG] 有效player_id响应: 200, Content-Type: image/png
[PASS] 验证1: 有效player_id返回200
[STEP] 测试恶意player_id...
[PASS] payload 1 被拒绝 (404): ../../etc/passwd...
[PASS] payload 2 被拒绝 (404): ../../../etc/passwd...
[PASS] payload 3 被拒绝 (404): ..%2F..%2Fetc%2Fpasswd...
[PASS] payload 4 被拒绝 (404): ' OR '1'='1...
[PASS] payload 5 被拒绝 (404): '; DROP TABLE users; --...
[PASS] payload 6 被拒绝 (404): ; cat /etc/passwd...
[PASS] payload 7 被拒绝 (404): | cat /etc/passwd...
[PASS] payload 8 被拒绝 (404): `cat /etc/passwd`...
[PASS] payload 9 被拒绝 (404): <script>alert(1)</script>...
[PASS] payload 10 被拒绝 (404): <img src=x onerror=alert(1)>...
[PASS] payload 11 被拒绝 (404): ...
[PASS] payload 12 被拒绝 (404): ...
[PASS] payload 13 被拒绝 (404): null...
[PASS] payload 14 被拒绝 (404): undefined...
[PASS] payload 15 被拒绝 (404): aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...
[PASS] payload 16 被拒绝 (404): %00...
[PASS] payload 17 被拒绝 (404): ...
[STEP] 验证返回内容类型...
[PASS] 验证3: 无效player_id返回 404
[PASS] TC-S-019 头像接口参数验证 --- 通过
--- 6.5 前端逻辑安全 结果: 3 通过, 0 失败 ---
5.2.6 服务端安全

html
===== 6.6 服务端安全 =====
--- TC-S-020: 路径穿越防护 ---
[STEP] 测试路径穿越payload...
[PASS] payload 1 被拦截 (HTTP 404): /../../../etc/passwd
[PASS] payload 2 被拦截 (HTTP 404): /players.html/../../../etc/passwd
[PASS] payload 3 被拦截 (HTTP 404): /players.html/../../etc/passwd
[PASS] payload 4 被拦截 (HTTP 404): /players.html/../etc/passwd
[PASS] payload 5 被拦截 (HTTP 404): /../../../../../../etc/passwd
[PASS] payload 6 被拦截 (HTTP 404): /../../../../../../etc/shadow
[PASS] payload 7 被拦截 (HTTP 404): /../../../../../../etc/hosts
[PASS] payload 8 被拦截 (HTTP 404): /..%2F..%2F..%2Fetc%2Fpasswd
[PASS] payload 9 被拦截 (HTTP 404): /%2e%2e/%2e%2e/%2e%2e/etc/passwd
[PASS] payload 10 被拦截 (HTTP 404): /..%252f..%252f..%252fetc%252fpasswd
[PASS] payload 11 被拦截 (HTTP 404): /..//../..//etc/passwd
[PASS] payload 12 被拦截 (HTTP 404): //..//..//etc/passwd
[PASS] payload 13 被拦截 (HTTP 404): /./../../../etc/passwd
[PASS] payload 14 被拦截 (HTTP 404): /././../../../etc/passwd
[PASS] payload 15 被拦截 (HTTP 404): /../../../etc/hostname
[PASS] payload 16 被拦截 (HTTP 404): /../../../proc/self/cmdline
[PASS] payload 17 被拦截 (HTTP 404): /../../../proc/version
[PASS] payload 18 被拦截 (HTTP 404): /../../../source/http/game.cpp
[PASS] payload 19 被拦截 (HTTP 404): /../../../source/http/auth.hpp
[PASS] payload 20 被拦截 (HTTP 404): /..\..\..\windows\system32\config\sam
[PASS] payload 21 被拦截 (HTTP 404): /..\..\..\etc\passwd
[RESULT] 21/21 个payload被拦截
[STEP] 测试ValidPath边界情况...
[PASS] 边界路径1 重定向 (302→/login.html): /players.html
[PASS] 边界路径2 被拦截 (404): /players.html/
[PASS] 边界路径3 重定向 (302→/login.html): //players.html
[PASS] 边界路径4 重定向 (302→/login.html): /./players.html
[PASS] 边界路径5 被拦截 (404): /players.html/.
[PASS] 边界路径6 重定向 (302→/login.html): /players.html/..
[PASS] 边界路径7 重定向 (302→/login.html): /players.html/../
[PASS] TC-S-020 路径穿越防护 --- 通过
--- TC-S-021: 静态文件目录限制 ---
[STEP] 尝试访问系统敏感文件...
[PASS] 文件1 被拦截 (404): /etc/passwd
[PASS] 文件2 被拦截 (404): /etc/shadow
[PASS] 文件3 被拦截 (404): /etc/hosts
[PASS] 文件4 被拦截 (404): /etc/hostname
[PASS] 文件5 被拦截 (404): /proc/self/cmdline
[PASS] 文件6 被拦截 (404): /proc/version
[PASS] 文件7 被拦截 (404): /proc/self/environ
[PASS] 文件8 被拦截 (404): /root/.bashrc
[PASS] 文件9 被拦截 (404): /root/.ssh/id_rsa
[PASS] 文件10 被拦截 (404): /var/log/auth.log
[PASS] 文件11 被拦截 (404): /var/log/syslog
[RESULT] 11/11 个敏感文件被拦截
[STEP] 尝试访问应用源代码文件...
[PASS] 应用文件被拦截 (404): /source/http/game.cpp
[PASS] 应用文件被拦截 (404): /source/http/auth.hpp
[PASS] 应用文件被拦截 (404): /source/http/main.cpp
[PASS] 应用文件被拦截 (404): /Makefile
[PASS] 应用文件被拦截 (404): /CMakeLists.txt
[PASS] TC-S-021 静态文件目录限制 --- 通过
--- TC-S-022: 请求方法限制 ---
[STEP] POST方法请求 /players.html...
[DEBUG] POST /players.html 响应: 404
[INFO] 验证1: POST返回 404
[STEP] PUT方法请求 /players.html...
[DEBUG] PUT /players.html 响应: 404
[PASS] 验证2: PUT返回 404
[STEP] DELETE方法请求 /players.html...
[DEBUG] DELETE /players.html 响应: 404
[PASS] 验证3: DELETE返回 404
[STEP] HEAD方法请求 /players.html...
[DEBUG] HEAD /players.html 响应: 302
[PASS] 验证4: HEAD重定向(认证拦截)
[STEP] OPTIONS方法请求 /players.html...
[DEBUG] OPTIONS /players.html 响应: 400
[INFO] 验证5: OPTIONS返回 400
[PASS] TC-S-022 请求方法限制 --- 通过
--- TC-S-023: URL超长拒绝 ---
[STEP] 发送超长URL...
[DEBUG] 超长URL响应: 414
[PASS] 验证1: 返回414 URI Too Long
[STEP] 发送超长查询参数...
[DEBUG] 超长查询参数响应: 414
[PASS] 验证2: 返回414 URI Too Long
[STEP] 测试边界长度...
[PASS] 长度 8000: 返回 404
[PASS] 长度 8192: 返回414
[PASS] 长度 8193: 返回414
[PASS] 长度 8500: 返回414
[PASS] 长度 9000: 返回414
[PASS] TC-S-023 URL超长拒绝 --- 通过
--- TC-S-024: Header超长拒绝 ---
[STEP] 发送超长Cookie...
[DEBUG] 超长Cookie响应: 414
[PASS] 验证1: 返回414
[STEP] 发送超长自定义Header...
[DEBUG] 超长自定义Header响应: 414
[PASS] 验证2: 返回 414
[STEP] 发送超长User-Agent...
[DEBUG] 超长User-Agent响应: 414
[PASS] 验证3: 返回 414
[PASS] TC-S-024 Header超长拒绝 --- 通过
--- TC-S-025: 并发连接耗尽防护 ---
[STEP] 并发请求: 20线程 x 5请求/线程...
[RESULT] 总请求: 100
[RESULT] 成功: 100
[RESULT] 错误: 0
[RESULT] 超时: 0
[RESULT] 成功率: 100.0%
[PASS] 验证1: 并发请求成功率≥80%
[STEP] 验证并发后服务器仍正常...
[PASS] 验证2: 并发后服务器仍正常响应
[PASS] TC-S-025 并发连接耗尽防护 --- 通过
--- TC-S-026: API参数注入 ---
[STEP] 检查API是否需要认证...
[FAIL] 安全漏洞! /api/players 未受认证保护!
[INFO] 以下API参数注入测试将在未认证状态下进行
[STEP] 测试 /api/players 的参数注入...
[PASS] payload 1 返回正常数据 (1个玩家): {'sort': '../../etc/passwd'}
[PASS] payload 2 返回正常数据 (1个玩家): {'order': '../../../etc/passwd'}
[PASS] payload 3 返回正常数据 (1个玩家): {'limit': '999999'}
[PASS] payload 4 返回正常数据 (1个玩家): {'offset': '-1'}
[PASS] payload 5 返回正常数据 (1个玩家): {'sort': "' OR '1'='1"}
[PASS] payload 6 返回正常数据 (1个玩家): {'order': "'; DROP TABLE users; --"}
[PASS] payload 7 返回正常数据 (1个玩家): {'sort': '; cat /etc/passwd'}
[PASS] payload 8 返回正常数据 (1个玩家): {'order': '| cat /etc/passwd'}
[PASS] payload 9 返回正常数据 (1个玩家): {'sort': '<script>alert(1)</script>'}
[PASS] payload 10 返回正常数据 (1个玩家): {'filter': '<img src=x onerror=alert(1)>'}
[PASS] payload 11 返回正常数据 (1个玩家): {'sort': ''}
[PASS] payload 12 返回正常数据 (1个玩家): {'sort': ' '}
[PASS] payload 13 返回正常数据 (1个玩家): {'sort': 'null'}
[PASS] payload 14 返回正常数据 (1个玩家): {'sort': 'undefined'}
[PASS] payload 15 返回正常数据 (1个玩家): {'sort': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'}
[STEP] 测试未知参数被忽略...
[PASS] 验证2: 未知参数被忽略,返回相同数据
[STEP] 测试 /api/leaderboard 的参数注入...
[PASS] payload 1 返回正常数据 (0条): ../../etc/passwd...
[PASS] payload 2 返回正常数据 (0条): ' OR '1'='1...
[PASS] payload 3 返回正常数据 (0条): ; cat /etc/passwd...
[PASS] payload 4 返回正常数据 (0条): <script>alert(1)</script>...
[PASS] payload 5 返回正常数据 (0条): ...
[PASS] payload 6 返回正常数据 (0条): aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...
[PASS] TC-S-026 API参数注入 --- 通过
--- 6.6 服务端安全 结果: 7 通过, 0 失败 ---


5.3 性能测试
5.1 页面加载
5.2 API接口响应
5.3 渲染性能
5.4 内存与资源
5.5 并发与极限
5.3.1 页面加载
TC-P-001 首次加载时间

TC-P-002 页面资源数量

TC-P-003 渲染阻塞资源

TC-P-004 首次内容绘制(FCP)

5.3.2 API接口响应
TC-P-005 /api/players响应时间

前置条件:有50条玩家记录


TC-P-006 10秒轮询对服务端压力



TC-P-007 头像接口响应时间



5.3.3 渲染性能
TC-P-008 统计卡片渲染时间

TC-P-009 玩家列表渲染(50玩家)
html
// 生成50个模拟玩家
const mockPlayers = Array.from({ length: 50 }, (_, i) => ({
id: `player_${i + 1}`,
name: `测试玩家${i + 1}`,
scores: {
snake: Math.floor(Math.random() * 1000),
minesweeper: Math.floor(Math.random() * 500),
gomoku: Math.floor(Math.random() * 200)
},
avatar: ''
}));
console.log(`生成了 ${mockPlayers.length} 个模拟玩家`);

TC-P-009 玩家列表渲染(50玩家)


TC-P-011 展开/折叠动画流畅度

TC-P-012 页面滚动流畅度

5.3.4 内存与资源
TC-P-016 并发客户端数量





TC-P-017 弱网环境(Slow 3G)
5.3.5 并发与极限
TC-P-018 大量玩家渲染(500人)
TC-P-019 快速连续刷新

5.4 易用性测试(略)
5.5 兼容性测试(略)





5.6 界面测试(略)





6. profile 页面


6.1 功能测试
1.1 页面初始化与登录验证

html
===== 1.1 页面初始化与登录验证 =====
--- TC-F-001: 已登录用户正常访问 ---
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[STEP] 执行登录流程并导航到 profile.html...
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] 验证1: 当前 URL = http://175.27.137.241:8081/profile.html
[PASS] 验证2: 登录界面未渲染
[PASS] 验证3: 主卡片已渲染且可见
[PASS] 验证4: 玩家昵称 = 'sy'
[PASS] 验证5: 登录账号: sy
[PASS] 验证6: 标题 = '🎮 个人中心'
[PASS] 验证7: 返回游戏链接存在
[PASS] TC-F-001 已登录用户正常访问 --- 全部验证通过
--- TC-F-002: 未登录用户访问自动跳转 ---
[STEP] 清除 Cookie 和 localStorage...
[STEP] 访问 profile.html (无 session)...
[PASS] 验证: 已自动跳转到 login.html (URL: http://175.27.137.241:8081/login.html)
[PASS] 验证: login.html 已正常加载(验证码可见)
[PASS] TC-F-002 未登录用户访问自动跳转 --- 通过
--- TC-F-006: 页面Title正确 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] 页面 title = '个人中心 - 经典小游戏'
[PASS] TC-F-006 页面Title正确 --- 通过
--- TC-F-007: Loading状态显示 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[STEP] 访问 profile.html 并检查 loading 状态...
[PASS] 验证1: Loading 状态已结束
[PASS] 验证2: 主卡片已显示
[PASS] TC-F-007 Loading状态显示 --- 通过
--- 1.1 页面初始化 结果: 4 通过, 0 失败 ---
[MANUAL] TC-F-003 Session过期: 需等待1小时后验证
[MANUAL] TC-F-004 profile网络异常: 需手动断网验证
[MANUAL] TC-F-005 profile返回非JSON: 需模拟服务端异常
TC-F-004 profile网络异常

TC-F-005 profile返回非JSON: 需模拟服务端异常
修改源代码


1.2 用户资料展示

html
===== 1.2 用户资料展示 =====
--- TC-F-008: 用户名正常显示 ---
[PASS] 验证1: 玩家昵称 = 'sy'
[PASS] 验证2: 登录账号: sy
[PASS] TC-F-008 用户名正常显示 --- 通过
--- TC-F-009: 玩家ID正常显示 ---
[PASS] 验证1: 玩家ID = 'client_sy'
[PASS] 验证2: 字体 = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
[PASS] TC-F-009 玩家ID正常显示 --- 通过
--- TC-F-010: 加入时间正常显示 ---
[PASS] 验证1: 加入时间 = '2026年6月7日'
[PASS] 验证2: 日期格式正确 (zh-CN)
[PASS] TC-F-010 加入时间正常显示 --- 通过
--- TC-F-012: player_name与username不同 ---
[STEP] 原始昵称: 'sy'
[STEP] 修改昵称为: 'TestUser_5981'
[PASS] 验证1: 昵称修改成功 Toast 显示
[PASS] 验证2: 昵称已更新为 'TestUser_5981'
[PASS] 验证3: 登录账号: sy
[STEP] 恢复昵称为: 'sy'
[PASS] TC-F-012 player_name与username不同 --- 通过
--- TC-F-013: 信息区域布局 ---
[PASS] 验证1: 信息区域容器存在
[PASS] 验证2: 使用 Grid 布局
[PASS] 验证3: 包含 2 个信息项
[PASS] TC-F-013 信息区域布局 --- 通过
--- 1.2 用户资料展示 结果: 5 通过, 0 失败 ---
[MANUAL] TC-F-011 加入时间为未知: 需要特殊数据状态验证
TC-F-011 加入时间为未知


1.3 游戏积分展示

html
===== 1.3 游戏积分展示 =====
--- TC-F-014: 有积分时正常显示 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 已设置排行榜 playerId: p_1777645482649_cupxpdd8g
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[PASS] 验证1: 显示 3 张积分卡片
[PASS] 验证2: 每张卡片都有图标、名称、分数
[PASS] 验证3: 卡片1分数 = 200
[PASS] 验证3: 卡片2分数 = 50
[PASS] 验证3: 卡片3分数 = 100
[PASS] 验证4: 卡片1名称 = '五子棋'
[PASS] 验证4: 卡片2名称 = '扫雷'
[PASS] 验证4: 卡片3名称 = '贪吃蛇'
[PASS] TC-F-014 有积分时正常显示 --- 通过
--- TC-F-016: 无积分时显示空状态 ---
[SKIP] 当前用户有积分,跳过空状态测试
[INFO] 需要无积分数据的用户才能测试此用例
--- TC-F-017: 点击返回游戏列表按钮 ---
[SKIP] 当前用户有积分,无空状态按钮,跳过此用例
--- TC-F-019: 积分卡片图标正确 ---
[PASS] 验证: 五子棋 图标 = '⚫'
[PASS] 验证: 扫雷 图标 = '💣'
[PASS] 验证: 贪吃蛇 图标 = '🐍'
[PASS] TC-F-019 积分卡片图标正确 --- 通过
--- TC-F-020: 积分卡片名称正确 ---
[PASS] 验证: 卡片1名称 = '五子棋'
[PASS] 验证: 卡片2名称 = '扫雷'
[PASS] 验证: 卡片3名称 = '贪吃蛇'
[PASS] TC-F-020 积分卡片名称正确 --- 通过
--- 1.3 游戏积分展示 结果: 5 通过, 0 失败 ---
[MANUAL] TC-F-015 积分卡片hover效果: CSS效果,需肉眼观察
[MANUAL] TC-F-018 部分游戏有积分: 需要特殊数据状态验证

TC-F-015 积分卡片hover效果

TC-F-016: 无积分时显示空状态

TC-F-017: 点击返回游戏列表按钮

1.4 头像功能


html
===== 1.4 头像功能 =====
--- TC-F-021: 默认字母头像显示 ---
[PASS] 验证1: 默认头像圆形容器存在
[PASS] 验证2: 显示首字母 's'
[PASS] 验证3: 圆形样式 (border-radius: 50%)
[PASS] 验证4: 渐变背景
[PASS] TC-F-021 默认字母头像显示 --- 通过
--- TC-F-022: 头像首字母取值 ---
[STEP] 玩家昵称: 'sy'
[PASS] 验证: 首字母 's' = 昵称首字符 's'
[PASS] TC-F-022 头像首字母取值 --- 通过
--- TC-F-025: 点击头像触发上传 ---
[PASS] 验证1: file input 存在, accept='image/*'
[PASS] 验证2: file input 是隐藏的
[PASS] 验证3: 头像区域可点击
[PASS] TC-F-025 点击头像触发上传 --- 通过
--- TC-F-027: 上传图片文件过大 ---
[PASS] 验证: Toast 显示 '图片最大支持 2MB'
[PASS] TC-F-027 上传图片文件过大 --- 通过
--- TC-F-028: 上传非图片文件 ---
[PASS] 验证: Toast 显示 '请选择图片文件'
[PASS] TC-F-028 上传非图片文件 --- 通过
--- TC-F-032: 上传后清空input ---
[PASS] 验证: 上传后 input 已清空
[PASS] TC-F-032 上传后清空input --- 通过
--- 1.4 头像功能 结果: 6 通过, 0 失败 ---
[MANUAL] TC-F-023 自定义头像加载: 需先上传头像后验证
[MANUAL] TC-F-024 头像加载失败降级: 需模拟404
[MANUAL] TC-F-026 头像hover遮罩: CSS效果,需肉眼观察
[MANUAL] TC-F-029 上传头像成功: 需准备测试图片
[MANUAL] TC-F-030 上传服务端失败: 需模拟服务端异常
[MANUAL] TC-F-031 上传网络异常: 需模拟网络断连
[MANUAL] TC-F-033 释放旧blobURL: 需验证内存
TC-F-023 自定义头像加载


TC-F-024 头像加载失败降级


TC-F-026 头像hover显示遮罩


TC-F-029 上传头像成功


TC-F-030 上传服务端失败


TC-F-031 上传网络异常


1.5 昵称修改功能


cpp
===== 1.5 昵称修改功能 =====
--- TC-F-034: 点击编辑图标进入编辑模式 ---
[STEP] 当前昵称: 'sy'
[PASS] 验证1: 编辑图标存在
[PASS] 验证2: 输入框出现
[PASS] 验证3: 输入框预填 'sy'
[PASS] 验证4: 保存按钮出现
[PASS] 验证5: 取消按钮出现
[PASS] 验证6: 输入框自动聚焦
[PASS] TC-F-034 点击编辑图标进入编辑模式 --- 通过
--- TC-F-035: 输入框maxlength限制 ---
[PASS] 验证1: maxlength = 32
[PASS] 验证2: 输入被限制为 32 字符
[PASS] TC-F-035 输入框maxlength限制 --- 通过
--- TC-F-036: Enter键保存昵称 ---
[STEP] 按 Enter 键...
[PASS] 验证1: Toast 显示 '昵称修改成功'
[PASS] 验证2: 昵称已更新为 'EnterTest_3058'
[STEP] 恢复昵称为: 'sy'
[PASS] TC-F-036 Enter键保存昵称 --- 通过
--- TC-F-037: 点击保存按钮 ---
[STEP] 点击保存按钮...
[PASS] 验证1: Toast 显示 '昵称修改成功'
[PASS] 验证2: 昵称已更新为 'SaveTest_3060'
[STEP] 恢复昵称为: 'sy'
[PASS] TC-F-037 点击保存按钮 --- 通过
--- TC-F-038: 昵称保存成功 ---
[PASS] 验证1: Toast 显示 '昵称修改成功'
[PASS] 验证2: 昵称已更新为 'SuccessTest_3062'
[PASS] 验证3: localStorage playerName = 'SuccessTest_3062'
[PASS] 验证4: 已退出编辑模式
[STEP] 恢复昵称为: 'sy'
[PASS] TC-F-038 昵称保存成功 --- 通过
--- TC-F-041: 昵称为空保存 ---
[PASS] 验证: Toast 显示 '昵称不能为空'
[PASS] TC-F-041 昵称为空保存 --- 通过
--- TC-F-042: 昵称仅空格 ---
[PASS] 验证: Toast 显示 '昵称不能为空'
[PASS] TC-F-042 昵称仅空格 --- 通过
--- TC-F-043: 昵称未修改直接保存 ---
[PASS] 验证1: 已退出编辑模式
[PASS] 验证2: 昵称未变 'sy'
[PASS] TC-F-043 昵称未修改直接保存 --- 通过
--- TC-F-044: 昵称已被占用 ---
[SETUP] 已注册临时用户 'taken_43067' 用于测试昵称占用
[PASS] 验证: Toast 显示 '该昵称已被注册'
[PASS] TC-F-044 昵称已被占用 --- 通过
--- TC-F-045: 取消编辑 ---
[PASS] 验证1: 已退出编辑模式
[PASS] 验证2: 昵称恢复为 'sy'
[PASS] TC-F-045 取消编辑 --- 通过
--- TC-F-046: 编辑提示文字 ---
[PASS] 验证: 提示文字 = '修改后游戏记录中的名字将同步更新'
[PASS] TC-F-046 编辑提示文字 --- 通过
--- 1.5 昵称修改功能 结果: 11 通过, 0 失败 ---
[MANUAL] TC-F-039 昵称保存失败-服务端错误: 需模拟服务端异常
[MANUAL] TC-F-040 昵称保存失败-网络异常: 需模拟网络断连
1.6 导航链接

html
===== 1.6 导航链接 =====
--- TC-F-047: 返回游戏链接 ---
[PASS] 验证1: 返回游戏链接存在, 文字='⬅ 返回游戏'
[PASS] 验证2: 链接指向 'http://175.27.137.241:8081/games.html'
[STEP] 点击返回游戏链接...
[PASS] 验证3: 已跳转到 games.html (URL: http://175.27.137.241:8081/games.html)
[PASS] TC-F-047 返回游戏链接 --- 通过
--- 1.6 导航链接 结果: 1 通过, 0 失败 ---
[MANUAL] TC-F-048 返回游戏链接hover样式: CSS效果,需肉眼观察

6.2 安全测试
6.1 认证与授权
6.2 XSS防护
6.3 CSRF防护
6.4 输入安全
6.5 路径穿越防护
6.6 信息泄露
6.7 业务逻辑安全
6.2.1 认证与授权

html
===== 6.1 认证与授权安全 =====
--- TC-S-001: 未登录直接访问 profile.html ---
[PASS] 验证1: HTTP 302 重定向
[PASS] 验证2: Location = '/login.html'
[PASS] 验证3: 未返回 profile 页面内容
[PASS] TC-S-001 未登录直接访问 --- 通过
--- TC-S-002: 伪造 Session Cookie 访问 ---
[PASS] 验证1: HTTP 302 重定向
[PASS] 验证2: Location = '/login.html'
[PASS] 验证3: 空 Session Cookie 处理正确 (302)
[PASS] TC-S-002 伪造 Session Cookie 访问 --- 通过
--- TC-S-003: 过期 Session 访问 ---
[PASS] 验证1: HTTP 302 重定向 (拒绝无效 token)
[PASS] 无效 token 'aaaaaaaaaaaaaaaaaaaa...' → 302 重定向
[PASS] 无效 token '00000000-0000-0000-0...' → 302 重定向
[PASS] 无效 token '../etc/passwd...' → 302 重定向
[PASS] TC-S-003 过期/无效 Session 访问 --- 通过
[MANUAL] 真正过期(>1h)的 session 测试需手动: 登录后等待 1h 再刷新
--- TC-S-004: 直接访问 API 绕过认证 ---
[PASS] 验证1: API 响应 HTTP 200
[INFO] API 返回非 JSON: '{"status":"error","message":"未登录"}'
[PASS] 验证3: /api/user/update-username 也拒绝未登录请求
[PASS] TC-S-004 直接访问 API 绕过认证 --- 通过
--- TC-S-005: 修改他人昵称防护 ---
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 API 登出清除活跃 session
[SETUP] 受害者用户 'victim_97381' 注册成功
[SETUP] 已通过 API 登出清除活跃 session
[SETUP] 测试用户 player_id: client_sy
[STEP] 以 sy 身份尝试修改 victim_97381 的昵称...
[PASS] 验证1: status=error, message='无权修改其他用户昵称'
[PASS] 验证2: 服务端明确拒绝 (message: '无权修改其他用户昵称')
[INFO] 混合参数测试结果: {'status': 'error', 'message': '该昵称已被注册'}
[PASS] TC-S-005 修改他人昵称防护 --- 通过
--- TC-S-006: 上传头像需登录 ---
[PASS] 验证1: API 响应 HTTP 200
[PASS] 验证2: status=error, message='未登录'
[PASS] 验证3: 伪造 Cookie 也被拒绝
[PASS] TC-S-006 上传头像需登录 --- 通过
--- 6.1 认证与授权安全 结果: 6 通过, 0 失败 ---
[MANUAL] TC-S-003 真正过期(>1h): 登录后等待 1 小时后刷新验证
6.2.2 XSS防护

html
===== 6.2 XSS 防护 =====
--- TC-S-007: 昵称 XSS 向量 ---
[SETUP] 执行完整登录流程...
[SETUP] 测试用户 'sy' 注册成功
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[SETUP] 登录完成,当前页面: http://175.27.137.241:8081/games.html
[STEP] 原始昵称: 'sy'
[TEST] 向量 1: '<script>alert('xss')</script>'
[INFO] 页面显示: '<script>alert('xss')</script>'
[PASS] XSS 向量 1 未注入 HTML 元素
[PASS] 页面未跳转
[TEST] 向量 2: '<img src=x onerror=alert(1)>'
[INFO] 页面显示: '<img src=x onerror=alert(1)>'
[PASS] XSS 向量 2 未注入 HTML 元素
[PASS] 页面未跳转
[TEST] 向量 3: '<svg onload=alert(1)>'
[INFO] 页面显示: '<svg onload=alert(1)>'
[PASS] XSS 向量 3 未注入 HTML 元素
[PASS] 页面未跳转
[TEST] 向量 4: ''"><script>alert(1)</script>'
[INFO] 页面显示: ''"><script>alert(1)</script>'
[PASS] XSS 向量 4 未注入 HTML 元素
[PASS] 页面未跳转
[TEST] 向量 5: 'javascript:alert(1)'
[INFO] 页面显示: 'javascript:alert(1)'
[PASS] XSS 向量 5 未注入 HTML 元素
[PASS] 页面未跳转
[STEP] 恢复原始昵称: 'sy'
[PASS] TC-S-007 昵称 XSS 向量 --- 通过
--- TC-S-008: 昵称含 HTML 标签 ---
[TEST] HTML 向量 1: '<img src=x onerror=alert(1)>'
[INFO] 页面显示: '<img src=x onerror=alert(1)>'
[PASS] HTML 向量 1 被正确转义,未解析为 DOM
[TEST] HTML 向量 2: '<a href='javascript:alert(1)'>click</a>'
[INFO] 页面显示: '<a href='javascript:alert(1)'>click</a>'
[PASS] HTML 向量 2 被正确转义,未解析为 DOM
[TEST] HTML 向量 3: '<div onclick=alert(1)>click</div>'
[INFO] 页面显示: '<div onclick=alert(1)>click</div>'
[PASS] HTML 向量 3 被正确转义,未解析为 DOM
[TEST] HTML 向量 4: '<iframe src=javascript:alert(1)>'
[INFO] 页面显示: '<iframe src=javascript:alert(1)>'
[PASS] HTML 向量 4 被正确转义,未解析为 DOM
[TEST] HTML 向量 5: '<body onload=alert(1)>'
[INFO] 页面显示: '<body onload=alert(1)>'
[PASS] HTML 向量 5 被正确转义,未解析为 DOM
[STEP] 恢复原始昵称: 'sy'
[PASS] TC-S-008 昵称含 HTML 标签 --- 通过
--- TC-S-009: 昵称含特殊字符 ---
[TEST] HTML 实体: '&<>"'/'
[PASS] 页面正常渲染
[PASS] HTML 结构完整
[PASS] 页面标题正常: '个人中心 - 经典小游戏'
[TEST] Unicode 特殊: '①②③αβγ'
[PASS] 页面正常渲染
[PASS] HTML 结构完整
[PASS] 页面标题正常: '个人中心 - 经典小游戏'
[TEST] Emoji: '😀🎮🐍💣'
[PASS] 页面正常渲染
[PASS] HTML 结构完整
[PASS] 页面标题正常: '个人中心 - 经典小游戏'
[TEST] 混合攻击: 'test<>&"'><script>'
[PASS] 页面正常渲染
[PASS] HTML 结构完整
[PASS] 页面标题正常: '个人中心 - 经典小游戏'
[TEST] 纯特殊: '!@#$%^&*()_+-=[]{}|;:,.<>?/'
[PASS] 页面正常渲染
[PASS] HTML 结构完整
[PASS] 页面标题正常: '个人中心 - 经典小游戏'
[STEP] 恢复原始昵称: 'sy'
[PASS] TC-S-009 昵称含特殊字符 --- 通过
--- TC-S-010: 玩家 ID 显示安全 ---
[INFO] 玩家ID 显示值: 'client_sy'
[INFO] 玩家ID DOM 子元素: False
[SETUP] 已通过 Redis 清除活跃 session
[PASS] 验证1: player_id 不包含特殊字符, safe_id 过滤生效
[PASS] 验证2: player_id='client_sy' 可安全用于文本插值
[PASS] TC-S-010 玩家 ID 显示安全 --- 通过
--- 6.2 XSS防护 结果: 4 通过, 0 失败 ---
6.2.3 CSRF防护

html
===== 6.3 CSRF 防护 =====
--- TC-S-011: 修改昵称 CSRF ---
[PASS] 测试1: 不带 Cookie 请求, HTTP 200
[PASS] CSRF 攻击被拒绝: 请先登录
[PASS] 测试2: 伪造 Cookie 请求也被拒绝
[SETUP] 已通过 Redis 清除活跃 session
[INFO] 正常请求结果: {'status': 'error', 'message': '该昵称已被注册'}
[PASS] 测试3: 有效 Cookie 的正常请求可被处理
[PASS] TC-S-011 修改昵称 CSRF --- 通过
--- TC-S-012: 上传头像 CSRF ---
[PASS] 测试1: 不带 Cookie 上传头像, HTTP 200
[PASS] CSRF 头像上传被拒绝: 未登录
[INFO] 无效 Cookie 请求结果: {'status': 'error', 'message': '未登录'}
[SETUP] 已通过 Redis 清除活跃 session
[PASS] 测试3: GET /api/user/avatar 不是修改操作 (HTTP 404)
[PASS] TC-S-012 上传头像 CSRF --- 通过
--- TC-S-013: Session Cookie SameSite ---
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[PASS] 验证1: 验证码请求成功
[PASS] 验证2: 登录成功
[INFO] Set-Cookie 响应头: 'GAME_SESSION=9d8ec233cd1afeb84c77abf5fbeea556c531682753755ea05933dc990eabdafe; Path=/; HttpOnly; SameSite=Lax; Max-Age=3600'
[INFO] GAME_SESSION Cookie 属性:
- name: GAME_SESSION
- secure: False
- httponly: False
[PASS] 验证3: SameSite 属性已设置
[PASS] 验证4: HttpOnly 属性已设置 (防 XSS 窃取 Cookie)
[INFO] Secure 属性未设置 (HTTP 环境正常)
[PASS] 验证5: Path=/ 属性已设置
[PASS] TC-S-013 Session Cookie SameSite --- 通过
--- 6.3 CSRF防护 结果: 3 通过, 0 失败 ---
[NOTE] TC-S-011/012 的完整 CSRF 验证需要从不同源发起请求
[NOTE] 可使用以下 curl 命令手动验证跨域 CSRF:
curl -X POST http://175.27.137.241:8081/api/user/update-username \
-d 'new_name=hacked&old_name=sy'

6.2.4 输入安全

html
===== 6.4 输入安全 =====
[NOTE] TC-S-014~018 与功能测试 TC-F-027/028/035/041/042/044 有重叠
[NOTE] 本模块侧重于从安全视角的后端绕过测试
--- TC-S-014: 昵称长度限制 (后端安全) ---
[NOTE] 前端 maxlength=32 已在 TC-F-035 测试,此处测试后端防护
[SETUP] 测试用户 'sy' 已存在,直接使用
[SETUP] 已通过 Redis 清除活跃 session
[SETUP] 已通过 API 登出清除活跃 session
[STEP] 发送 1000 字符昵称通过 API (绕过前端限制)
[PASS] 后端拒绝了超长昵称: '该昵称已被注册'
[PASS] 空昵称 API 请求被后端拒绝: 昵称无效
[PASS] TC-S-014 昵称长度限制 --- 通过
[NOTE] TC-F-035 已测试前端 maxlength=32,后端安全在此验证
--- TC-S-015: 昵称为空校验 (后端验证) ---
[NOTE] 前端空昵称拦截已在 TC-F-041/042 测试,此处验证后端
[PASS] '空字符串' 被后端拒绝: '昵称无效'
[PASS] '仅空格' 被后端拒绝: '该昵称已被注册'
[PASS] '仅制表符' 被后端拒绝: '该昵称已被注册'
[PASS] '换行符' 被后端拒绝: '该昵称已被注册'
[PASS] '混合空白' 被后端拒绝: '该昵称已被注册'
[PASS] TC-S-015 昵称为空校验 --- 通过
--- TC-S-016: 昵称重名检查 (后端验证) ---
[NOTE] 前端重名 Toast 已在 TC-F-044 测试,此处验证后端
[SETUP] 注册临时用户 'input_sec_test_97391' 用于重名测试
[PASS] 后端重名检查正确: '该昵称已被注册'
[PASS] TC-S-016 昵称重名检查 --- 通过
--- TC-S-017: 头像文件类型校验 (后端验证) ---
[NOTE] 前端 file.type 检查已在 TC-F-028 测试,此处验证后端
[INFO] 非图片 Base64 上传结果: status=error, message='仅支持 PNG/JPG/GIF/WebP 格式'
[PASS] 后端拒绝了非图片数据: '仅支持 PNG/JPG/GIF/WebP 格式'
[PASS] 后端拒绝了无效 Base64 数据
[PASS] TC-S-017 头像文件类型校验 --- 通过
--- TC-S-018: 头像文件大小校验 (后端验证) ---
[NOTE] 前端 2MB 检查已在 TC-F-027 测试,此处验证后端
[STEP] 发送约 0.7MB 的 Base64 图片数据 (绕过前端 2MB 限制)
[INFO] 中等大小文件结果: status=ok, message='头像上传成功'
[INFO] 后端接受了中等大小文件 (在限制范围内)
[PASS] TC-S-018 头像文件大小校验 --- 通过
--- TC-S-019: 头像 Base64 解码安全 ---
[INFO] 正常格式: status=ok
[PASS] 测试1: 正常 Base64 格式处理成功
[PASS] 测试2: 空图片数据被拒绝: '图片数据无效'
[INFO] 路径穿越注入: status=error
[PASS] 测试3: 路径穿越注入被处理
[PASS] TC-S-019 头像 Base64 解码安全 --- 通过
--- 6.4 输入安全 结果: 6 通过, 0 失败 ---
6.2.5 路径穿越防护

html
===== 6.5 路径穿越防护 =====
--- TC-S-020: 头像路径穿越防护 ---
[SETUP] 已通过 Redis 清除活跃 session
[TEST] 向量 1: player_id='../etc/passwd...' → HTTP no_response
[INFO] HTTP no_response
[TEST] 向量 2: player_id='..\..\..\windows\system32\config\sam...' → HTTP no_response
[INFO] HTTP no_response
[TEST] 向量 3: player_id='../../../etc/shadow...' → HTTP no_response
[INFO] HTTP no_response
[TEST] 向量 4: player_id='....//....//....//etc/passwd...' → HTTP no_response
[INFO] HTTP no_response
[TEST] 向量 5: player_id='..%2f..%2f..%2fetc%2fpasswd...' → HTTP no_response
[INFO] HTTP no_response
[TEST] 向量 6: player_id='..%252f..%252f..%252fetc/passwd...' → HTTP no_response
[INFO] HTTP no_response
[TEST] 向量 7: player_id='/etc/passwd...' → HTTP no_response
[INFO] HTTP no_response
[TEST] 向量 8: player_id='C:\Windows\System32\drivers\etc\hosts...' → HTTP no_response
[INFO] HTTP no_response
[TEST] 向量 9: player_id='client_test/../../../etc/passwd...' → HTTP no_response
[INFO] HTTP no_response
[TEST] 向量 10: player_id='./../../etc/passwd...' → HTTP no_response
[INFO] HTTP no_response
[PASS] TC-S-020 头像路径穿越防护 --- 通过
--- TC-S-021: 头像文件路径限制 ---
[SETUP] 已通过 Redis 清除活跃 session
[INFO] 正常请求: HTTP 200
[INFO] 带 './' 请求: HTTP 200
[TEST] player_id='client_test' → HTTP no_response
[TEST] player_id='client_test.png' → HTTP no_response
[TEST] player_id='client_test%00.html' → HTTP no_response
[TEST] player_id='client_test.html' → HTTP no_response
[PASS] TC-S-021 头像文件路径限制 --- 通过
--- TC-S-022: 空 player_id 处理 ---
[PASS] TC-S-022 空 player_id 处理 --- 通过
--- 6.5 路径穿越防护 结果: 3 通过, 0 失败 ---
6.2.6 信息泄露

html
===== 6.6 信息泄露 =====
--- TC-S-023: 错误消息不泄露后端信息 ---
[SCENE 2] 未登录上传头像: message='未登录'
[PASS] 场景2: 未泄露敏感信息
[SETUP] 已通过 Redis 清除活跃 session
[PASS] TC-S-023 错误消息不泄露后端信息 --- 通过
--- TC-S-024: 响应头信息泄露 ---
[CHECK] /profile.html 响应头:
[PASS] Server 头不存在 (好!)
[PASS] X-Powered-By 头不存在 (好!)
[PASS] X-AspNet-Version 头不存在 (好!)
[PASS] X-AspNetMvc-Version 头不存在 (好!)
[PASS] X-Generator 头不存在 (好!)
[PASS] X-Drupal-Cache 头不存在 (好!)
[PASS] X-Drupal-Dynamic-Cache 头不存在 (好!)
[CHECK] /login.html 响应头:
[PASS] Server 头不存在 (好!)
[PASS] X-Powered-By 头不存在 (好!)
[PASS] X-AspNet-Version 头不存在 (好!)
[PASS] X-AspNetMvc-Version 头不存在 (好!)
[PASS] X-Generator 头不存在 (好!)
[PASS] X-Drupal-Cache 头不存在 (好!)
[PASS] X-Drupal-Dynamic-Cache 头不存在 (好!)
[CHECK] /api/user/profile 响应头:
[PASS] Server 头不存在 (好!)
[PASS] X-Powered-By 头不存在 (好!)
[PASS] X-AspNet-Version 头不存在 (好!)
[PASS] X-AspNetMvc-Version 头不存在 (好!)
[PASS] X-Generator 头不存在 (好!)
[PASS] X-Drupal-Cache 头不存在 (好!)
[PASS] X-Drupal-Dynamic-Cache 头不存在 (好!)
[CHECK] /api/auth/captcha?id=test 响应头:
[PASS] Server 头不存在 (好!)
[PASS] X-Powered-By 头不存在 (好!)
[PASS] X-AspNet-Version 头不存在 (好!)
[PASS] X-AspNetMvc-Version 头不存在 (好!)
[PASS] X-Generator 头不存在 (好!)
[PASS] X-Drupal-Cache 头不存在 (好!)
[PASS] X-Drupal-Dynamic-Cache 头不存在 (好!)
[PASS] 响应头无明显框架标识
[PASS] TC-S-024 响应头信息泄露 --- 通过
--- TC-S-025: 用户名枚举防护 ---
[NOTE] 此功能允许用户检查昵称可用性,属于预期行为
[INFO] 已存在用户注册: message='用户名已存在'
[INFO] 注册接口暴露了用户名存在性 (这是正常功能)
[PASS] 这是预期行为 --- 用户需要知道注册的用户名是否可用
[SETUP] 已通过 API 登出清除活跃 session
[INFO] 改为已存在昵称: message='该昵称已被注册'
[PASS] 这是预期功能 --- 用户应知道昵称是否可用
[INFO] 不存在用户登录: message='用户名或密码错误'
[PASS] 登录接口不区用户名存在性
[PASS] TC-S-025 用户名枚举防护 --- 通过
[NOTE] 注册/改名接口的昵称可用性检查是预期功能
--- TC-S-026: 头像无缓存 ---
[SETUP] 已通过 Redis 清除活跃 session
[INFO] HTTP 200
Cache-Control: 'no-cache, no-store, must-revalidate'
Pragma: ''
Expires: ''
[PASS] Cache-Control: no-cache
[PASS] Cache-Control: no-store
[PASS] Cache-Control: must-revalidate
[PASS] 第二次请求也正常完成 (HTTP 200)
[PASS] TC-S-026 头像无缓存 --- 通过
--- 6.6 信息泄露 结果: 4 通过, 0 失败 ---
6.2.7 业务逻辑安全

html
===== 6.7 业务逻辑安全 =====
--- TC-S-027: 改名后 Session 同步 ---
[STEP] 原始昵称: 'sy'
[PASS] 验证1: UI 昵称已更新为 'SessionSync_9354'
[PASS] 验证2: 刷新后昵称保持一致 'SessionSync_9354'
[PASS] 验证3: 改名后登录态仍有效 (可访问 games.html)
[STEP] 恢复昵称为: 'sy'
[PASS] TC-S-027 改名后 Session 同步 --- 通过
--- TC-S-028: 改名后排行榜同步 ---
[STEP] 原始昵称: 'sy'
[INFO] player_id: client_sy
[INFO] API 改名结果: {'status': 'ok', 'player_id': 'client_sy'}
[PASS] API 改名成功
[STEP] 已通过 API 恢复昵称为: 'sy'
[INFO] 恢复后昵称: 'sy'
[PASS] TC-S-028 改名后排行榜同步 --- 通过
[MANUAL] 完整排行榜同步验证: 改名后查看 games.html 排行榜显示
--- TC-S-029: 改名后活跃会话同步 ---
[STEP] 原始昵称: 'sy'
[INFO] 改名前的活跃会话: user:active_session:sy = 175.27.137.241
[PASS] 验证1: 昵称已更新
[PASS] 验证2: 旧活跃会话已清除 (user:active_session:sy 不存在)
[PASS] 验证3: 新活跃会话已创建 (user:active_session:ActiveSession_9357 = 175.27.137.241)
[STEP] 恢复昵称为: 'sy'
[PASS] TC-S-029 改名后活跃会话同步 --- 通过
--- TC-S-030: 头像覆盖攻击 ---
[SETUP] 已通过 Redis 清除活跃 session
[INFO] 测试用户 player_id: client_sy
[SETUP] 攻击者用户 'attacker_99359' 注册成功
[WARN] 登录失败: 用户名或密码错误
[SKIP] 无法获取攻击者 session,可能是验证码问题
[INFO] 头像覆盖攻击防护逻辑:
1. 服务端从 session 中提取当前登录用户
2. 头像文件路径使用当前用户的 safe_id
3. 攻击者即使传入他人的 player_id,文件仍保存在攻击者自己的路径下
[PASS] TC-S-030 头像覆盖攻击 --- 通过
--- TC-S-031: 并发改名竞态 ---
[SETUP] 已通过 Redis 清除活跃 session
[STEP] 发起 3 个并发改名请求...
[INFO] 并发改名结果 (3 个响应):
线程 0 → Race_9359_0: {'status': 'ok', 'player_id': 'client_sy'}
线程 2 → Race_9359_2: {'status': 'error', 'message': '无权修改其他用户昵称'}
线程 1 → Race_9359_1: {'status': 'error', 'message': '无权修改其他用户昵称'}
[STEP] 已恢复昵称为: 'sy'
[PASS] TC-S-031 并发改名竞态 --- 通过
[INFO] 并发测试验证了 Redis RenameUser 原子操作的基本安全性
--- 6.7 业务逻辑安全 结果: 5 通过, 0 失败 ---
[MANUAL] TC-S-028 完整排行榜同步: 需在 games.html 界面验证排行榜显示
[MANUAL] TC-S-029 活跃会话同步: Redis key 检查需要 Redis 访问权限
TC-S-028: 改名后排行榜同步 TC-S-029 活跃会话同步






6.3 性能测试
3.1 页面加载
3.2 API接口响应
3.3 渲染性能
3.4 内存与资源
3.5 并发与极限
6.3.1 页面加载

TC-P-001 首次加载时间

TC-P-002 页面资源大小



TC-P-003 外部CDN加载

6.3.2 API接口响应

TC-P-004 /api/user/profile响应时间

TC-P-005 /api/user/avatar响应时间(有头像)

TC-P-007 /api/user/update-username响应时间

TC-P-008 头像上传响应时间
根据请求Content-type 我们需要通过表单形式传递一个体积符合 500KB 左右的 Base64 字符串



6.3.3 渲染性能

TC-P-009 页面渲染时间

TC-P-010 积分卡片渲染(3个游戏)

TC-P-011 头像图片加载


6.3.4 内存与资源

TC-P-012 Blob URL内存管理



内存没有持续增长,旧资源的生命周期管理完全合格。
TC-P-013 页面长驻10分钟
TC-P-014 弱网环境(Slow 3G)



6.3.5 并发与极限

TC-P-015 并发修改昵称



TC-P-016 快速连续上传头像

TC-P-017 大文件上传超时

7. snake页面


7.1 功能测试
1.1 页面初始化与登录验证
1.2 游戏初始化
1.3 键盘控制
1.4 触摸控制(移动端)
1.5 碰撞检测与游戏结束
1.6 食物与得分
1.7 游戏结束弹窗
1.8 分数自动提交
1.9 排行榜功能
1.10 导航与UI交互
1.11 绘图渲染
7.1.1 页面初始化与登录验证



7.1.2 游戏初始化


7.1.3 键盘控制



7.1.4 触摸控制(移动端)

整体使用体验比较差,整个网页会随着手指也滑动
7.1.5 碰撞检测与游戏结束


7.1.6 食物与得分


7.1.7 游戏结束弹窗


7.1.8 分数自动提交



7.1.9 排行榜功能




7.1.10 导航与UI交互


7.1.11 绘图渲染



7.2 安全测试
2.1 认证与授权
2.2 分数提交安全
2.3 XSS防护
2.4 CSRF防护
2.5 输入安全
2.6 信息泄露
2.7 服务端安全
2.8 客户端逻辑安全
7.2.1 认证与授权


7.2.2 分数提交安全



html
==========================================
snake.html 安全漏洞验证脚本
==========================================
检查服务器状态...
服务器可达
==========================================
[漏洞1] Session验证绕过
==========================================
问题: HandleSubmitScore函数中Session验证代码被注释
位置: game.cpp 第443-448行
验证: 不带Cookie直接提交分数...
响应: {"status":"ok","message":"分数已记录"}
漏洞确认: 服务端接受无session的提交!
==========================================
[漏洞2] Player参数伪造
==========================================
问题: 未验证player参数与session用户的一致性
位置: game.cpp 第465-488行
验证: 冒充admin用户提交高分...
响应: {"status":"ok","message":"分数已记录"}
漏洞确认: 可以冒充其他用户提交分数!
==========================================
[漏洞3a] 异常大分数
==========================================
问题: 未校验分数范围上限
位置: game.cpp 第510-520行
验证: 提交分数=999999999...
响应: {"status":"ok","message":"分数已记录"}
漏洞确认: 接受异常大分数!
==========================================
[漏洞3b] 负数分数
==========================================
问题: 未校验分数范围下限
验证: 提交分数=-100...
响应: {"status":"ok","message":"分数已记录"}
漏洞确认: 接受负数分数!
==========================================
[漏洞3c] 非数字分数
==========================================
问题: std::stoi转换失败时catch块为空,score=0但仍接受提交
验证: 提交分数=abc...
响应: {"status":"ok","message":"分数已记录"}
漏洞确认: 接受非数字分数(转换为0)!
==========================================
[漏洞4] 频率限制缺失
==========================================
问题: 无任何频率限制机制
验证: 快速连续提交10次...
成功提交: 10/10
漏洞确认: 无频率限制,可无限提交!
==========================================
[查看排行榜]
==========================================
获取当前排行榜数据...
前5名:
1. test: 999999999分
2. admin: 100000分
3. Hacker: 99999分
4. Spammer: 10分
==========================================
验证完成
==========================================
修复建议:
1. 加入Session验证代码
2. 添加player参数验证(必须与session用户一致)
3. 添加分数范围校验(0 < score <= 合理上限)
4. 添加频率限制(同一用户60秒内不能重复提交)
7.2.3 XSS防护


7.2.4 CSRF防护


7.2.5 输入安全


7.2.6 信息泄露


7.3.7 服务端安全


7.3.8 客户端逻辑安全


7.3 性能测试
3.1 页面加载
3.2 游戏循环性能
3.3 API接口响应
3.4 内存与资源
3.5 并发与极限
7.3.1 页面加载

TC-P-001 首次加载时间

TC-P-002 页面资源数量

TC-P-003 渲染阻塞资源

TC-P-004: Canvas初始化时间

7.3.2 游戏循环性能

TC-P-005 step()函数执行时间

TC-P-007 长蛇身绘制性能

TC-P-008 setInterval精度

TC-P-009: 食物生成性能

7.3.3 API接口响应

TC-P-011 /api/auth/check响应时间(已做)
TC-P-012 /api/leaderboard响应时间(已做)
TC-P-013 /api/score提交响应时间

TC-P-014: 5秒轮询对服务端压力



7.3.4 内存与资源

TC-P-015: 长时间游戏内存稳定


TC-P-016: 快速重新开始50次

TC-P-017: 游戏结束弹窗反复开关


7.4.5 并发与极限

TC-P-020: 快速方向切换

TC-P-021: 同时按多个键

TC-P-022: 弱网环境(Slow 3G)



TC-P-023: 并发分数提交




8. minesweeper 页面


8.1 功能测试
1.1 页面初始化与登录验证
1.2 游戏板初始化
1.3 首次点击与地雷放置
1.4 格子点击与翻开
1.5 Flood Fill 展开
1.6 右键标志功能
1.7 踩雷与游戏结束
1.8 胜利判定
1.9 计时器
1.10 切换难度与新游戏
1.11 键盘快捷键
1.12 触摸控制
1.13 自适应布局
1.14 分数提交
1.15 排行榜功能
1.16 导航与UI交互
1.17 地雷计数与邻接计算
1.18 边界与极端条件
8.1.1 页面初始化与登录验证


8.1.2 游戏板初始化


8.1.3 首次点击与地雷放置


8.1.4 格子点击与翻开



8.1.5 Flood Fill 展开


8.1.6 右键标志功能


8.1.7 踩雷与游戏结束


8.1.8 胜利判定


8.1.9 计时器


8.1.10 切换难度与新游戏


8.1.11 键盘快捷键


8.1.12 触摸控制(略)

8.1.13 自适应布局(略)

8.1.14 分数提交



8.1.15 排行榜功能



8.1.16 导航与UI交互


8.1.17 地雷计数与邻接计算


8.1.18 边界与极端条件



8.2 性能测试
2.1 页面加载
2.2 游戏初始化与渲染
2.3 Flood Fill 性能
2.4 计时器性能
2.5 自适应布局性能
2.6 API接口响应
2.7 内存与资源
2.8 并发与极限
8.2.1 页面加载

TC-P-001 首次加载时间

TC-P-002 页面资源数量

TC-P-003 渲染阻塞资源

8.2.2 游戏初始化与渲染

TC-P-004 initGame 执行时间

TC-P-005 renderBoard 执行时间

TC-P-006 updateBoard 全量更新

TC-P-007 困难难度棋盘首次渲染

8.2.2 API接口响应

TC-P-015 /api/auth/check 响应时间(已测)
TC-P-016 /api/leaderboard(含 difficulty)响应时间



TC-P-017 /api/score 提交(含 difficulty 参数)响应时间



TC-P-018 5 秒轮询对服务端压力


8.2.3 内存与资源

TC-P-020 快速新游戏 50 次

TC-P-021 游戏结束弹窗反复开关 20 次



TC-P-023 难度切换反复 50 次


TC-P-024 页面离开定时器清除


8.2.3 并发与极限

TC-P-025 快速点击多个格子

TC-P-027 弱网环境(Slow 3G)

TC-P-028 并发分数提交



8.3 安全测试
3.1 认证与授权
3.2 分数提交安全
3.3 XSS防护
3.4 CSRF防护
3.5 输入安全
3.6 信息泄露
3.7 服务端安全
3.8 客户端逻辑安全
8.3.1 认证与授权


8.3.2 分数提交安全







8.3.3 XSS防护






8.3.4 CSRF防护


8.3.5 输入安全


8.3.6 信息泄露


8.3.7 服务端安全


8.3.8 客户端逻辑安全




9. gomoku页面


9.1 功能测试
1.1 页面初始化与登录验证
1.2 游戏板初始化
1.3 玩家落子
1.4 胜负判定
1.5 悔棋功能
1.6 新游戏/重新开局
1.7 计分与分数提交
1.8 AI逻辑
1.9 Canvas渲染
1.10 排行榜
1.11 键盘快捷键
1.12 导航与UI
9.1.1 页面初始化与登录验证


9.1.2 游戏板初始化


9.1.3 玩家落子


9.1.4 胜负判定


9.1.5 悔棋功能


9.1.6 新游戏/重新开局


9.1.7 计分与分数提交


9.1.8 AI逻辑

9.1.9 Canvas渲染

9.1.10 排行榜


9.1.11 键盘快捷键


9.1.12 导航与UI



9.2 性能测试
2.1 页面加载
2.2 游戏渲染性能
2.3 AI性能
2.4 API接口响应
2.5 内存与资源
9.2.1 页面加载



9.2.2 游戏渲染性能

TC-P-003 initGame 执行时间


TC-P-004 drawBoard 重绘时间



TC-P-005 drawStone 单颗棋子渲染


TC-P-006 Canvas 重绘与帧率


9.2.3 AI性能

TC-P-007 AI 思考时间

TC-P-009 悔棋后重绘

9.2.4 API接口响应

TC-P-010 /api/auth/check响应时间(已做)
TC-P-011 /api/leaderboard?game=gomoku响应时间



TC-P-012 /api/score提交响应时间


TC-P-013 5秒轮询对服务端压力



9.2.5 内存与资源

TC-P-014 长时间游戏内存稳定
TC-P-015 多局游戏后Canvas无泄漏


TC-P-016 页面离开定时器清除

9.3 安全测试
3.1 认证与授权
3.2 分数提交安全
3.3 XSS防护
3.4 CSRF防护
3.5 客户端逻辑安全
3.6 信息泄露
9.3.1 认证与授权


9.3.2 分数提交安全


9.3.3 XSS防护


9.3.4 CSRF防护


9.3.5 客户端逻辑安全


9.3.6 信息泄露




四.测试总结
1.bug管理
1.1.1 TC-F-003
a . 现象
一直重复显示需要注册,注册一直失败。
b . 原因
原服务器使用docker配备了一主二从的redis-server,还有三个哨兵监控。但是在轻量级应用服务器上这样的配置必定耗费一定的内存资源,特别是如果还有其他的进程消耗资源的情况下,所有在半夜redis-master被系统杀掉了,我发现之后重新启动,但是没有配置好之间的关系,导致无法将数据写入对应的数据库中,使得前端一直检验redis中没有对应的user,一直提示user注册。
c .解决方法
新增USE_REDIS_SENTINEL宏标志位(默认0):标志位为 1 则为直连模式,不编译任何哨兵代码,轻量无依赖。标志位为 2 则为哨兵主从模式,通过哨兵发现主库。在一台轻量级主机上配置哨兵主从模式也不太友好。
修改之后:
html
--- TC-F-003: 注册后自动填充登录 ---
[SETUP] 已清除主机注册记录 (host:reg:*)
[STEP] 打开注册模态框...
[STEP] 注册新用户: test_reg_48847
[STEP] 点击注册按钮...
[STEP] 验证注册后自动填充...
[CHECK] 用户名已自动填入: 'test_reg_48847'
[STEP] 等待验证码刷新...
[STEP] 输入密码并填写验证码...
[CHECK] 验证码答案: 85
[STEP] 点击登录(注册后自动填充)...
[CHECK] 页面已跳转至: http://175.27.137.241:8081/games.html
[PASS] TC-F-003 注册后自动填充登录成功
1.1.2 TC-F-016
a. 现象
验证码生成五分钟以上仍然有效可登录。
1.1.3 TC-U-001 TC-U-006 TC-U-025



a. 现象
页面未居中
入框默认状态:输入框文字颜色异常: 'rgb(224, 224, 224)'
模态框卡片:模态框宽度应在 200-600px 之间, 实际: 1272px
1.1.4 不同平台登录问题
在Edge浏览器登录之后仍然可以在Firefox浏览器登录,但是在Safari浏览器显示已经登录。



但是个人中心并没有数据

在FireFox退出登录又重新登录是新的玩家ID,但是是以前的数据




其他两个游戏能够正常玩。
1.2 games 页面
1.2.1 TC-F-16

a .现象
在无 session 的情况下直接访问175.27.137.241:8081/snake.html/minesweeper.html/gomoku.html
显示 404 Not Found , 没有设置重定向到 login.html

1.2.2 TC-F-020

a. 现象
模拟服务器端返回纯文本"text/plain",访问games.html的/api/players接口,依旧显示正常,resp.json()解析不异常,没有显示玩家信息加载失败,页面不崩溃。


这里还附加发现一个bug,发现请求palyer_id = p_1777645482649_cupxpdd8g头像失败,但是最开始显示是没有头像的,用户可以自己上传头像,可能是之前测试数据没有清除缓存。
1.2.3 TC-F-037


a. 现象
登录退出之后确实回到了login.html,但是点击浏览器回退按钮并没有退回到浏览器主页,也没有继续在login.html,而是回退到了games.html。
1.3 players 页面
1.3.1 TC-P-006: /api/... 等没有做安全性检查
a.现象


但是在浏览上直接访问http://175.27.137.241:8081/api/players 会重定向到login.html

说明后端压根就没有对这个 API 请求做任何拦截或重定向,是直接把数据放行给了没有 Cookie 的 Postman 。
c.解决方案
方法1. 应该采用"默认全部保护(白名单机制)":除了登录相关的几个特定 API,其他所有 **/api/**接口默认都必须登录。
源代码:

修改为:

方法2: 在方法路由的函数中添加检验




1.3.2 /api/leaderboard
同上

1.4 profile页面
1.4.1 TC-F-005 profile返回非JSON: 需模拟服务端异常

a.现象
后端做了特殊处理把GET /api/profile 返回格式修改为 text/plain; charset=utf-8

b. 原因

结论是:不算 bug,是有意的容错设计,但用户体验上有改进空间。

c. 解决方案
如果想改善这个体验,可以在 catch 里加一个错误状态页面。
1.4.2 TC-F-033 上传时释放旧blob URL
a.现象

修改头像后 LocalStorage 未更新

b. 原因



c. 解决方案



1.4.3 TC-P-005 /api/user/avatar响应时间(无头像)

a.现象
用户没有长传头像的时候不应该一直请求头像信息,应该使用默认的或者不请求。问题 :
每次都浪费一个请求,新用户/无头像用户每次都请求一个注定失败的 URL
404 是错误状态码,DevTools 显示红色,容易误以为是 Bug
不符合 RESTful 规范,资源不存在不应该用 404 来"正常处理"
用户体验差,网络慢时会有一个明显的加载失败过程

b.原因
同上

上传新头像之后就不会显示404 not found了

1.4.4 TC-P-005 /api/user/avatar响应时间(有头像)
a.现象
要求avg time <=100 ms 但是实际超过了


b.原因
问题分析:





c. 解决方案

TC-P-011 头像图片加载

a. 现象
图片加载时间严重超时

b. 原因
C++ Web 服务器收到请求后,直接去磁盘上同步读取文件,或者用很小的 buffer 循环读取并发送,高频的磁盘 I/O 会带来极大的延迟。
c. 解决方案
-
使用 Redis 做图片缓存:将高频访问的用户头像直接缓存在内存中,或者缓存其 base64 数据,避免每次都走文件系统读取。
-
动静分离 :不要让后端业务接口去处理图片等静态资源的传输。把上传的图片直接丢给 Nginx 托管,或者存到 OSS / CDN 上。Nginx 或 CDN 的高并发零拷贝(Zero-Copy)技术可以在几十毫秒内搞定 2MB 图片的传输。
-
前端进行图片压缩:在用户上传 2MB 头像时,前端先用 Canvas 进行等比缩放或质量压缩(压到 100KB 左右)再传给后端。头像通常不需要 3000x3000 并保持 2MB 的高清原图。
1.5 snake 页面
1.5.1 触摸控制(移动端)
a.现象
整体使用体验比较差,整个网页会随着手指也滑动。
1.5.2 TC-S-005 直接curl伪造分数提交

a.现象
任何人可以无限制提交高分 , 排行榜数据完全不可信 , 可以刷满整个排行榜。



b.原因
同上 , 没有在方法路由的函数中添加检验session检验。
1.5.3 TC-S-006 分数参数篡改

a.现象
可以冒充任何用户刷分数 , 无法区分真实分数和伪造分数。


b. 原因

1.5.4 TC-S-007 分数为负数提交
分数参数无范围校验。
排行榜可被垃圾数据污染 , 负数分数影响显示 , 无法保证分数真实性。
a.现象




1.5.5 TC-S-009 分数为非数字提交
1.5.6 TC-S-008 分数为小数提交
排行榜可被垃圾数据 , 污染负数分数影响显示 , 无法保证分数真实性。
a. 现象


b. 原因

1.5.7 无频率限制
代码中没有任何:
同一用户提交频率限制。
同一IP请求限制。
基于player_id的去重机制。
a. 现象


可以瞬间刷满排行榜
服务端资源被滥用
正常用户数据被淹没

1.5.8 Game参数未校验
可以创建任意游戏的排行榜
数据混乱
a. 现象



b. 原因

1.6 mimesweeper页面

1.6.1 TC-S-005: 无需登录即可提交任意分数

a. 现象


正确行为: 应返回 {"status":"error","message":"未登录"}
b. 原因
同1.5.2
1.6.2 TC-S-006: 分数无合理性校验

a. 现象



1.6.3 TC-S-010: 可冒用他人用户名提交分数

a. 现象

b. 原因
player 参数与当前 session 登录用户未做绑定,可冒用任意用户名。
1.6.4 TC-S-013: difficulty 参数写入文件未转义

a. 现象


1.6.5 TC-S-007/008/009: 分数输入类型校验缺失

a. 现象


b.原因
同1.5.6
1.6.6 TC-S-012: difficulty 未做合法值校验

a. 现象

1.6.7 TC-S-025: difficultySelect 篡改

a. 现象


1.6.8 TC-S-039/040/041/042: 客户端变量完全可控





1.7 gomoku 页面

同 minesweeper一样的问题。
仓库: Based on onethreadoneloop web games: Based on onethreadoneloop web games





































