# 搜索页 — 测试用例（C 端 · 全站）

> **依据：** 《搜索页功能需求.md》v1.0、《搜索页技术方案.md》v1.1  
> **关联：** C 端《商城首页测试用例.md》MHP-UI-002/003（SRH2/SRH15 入口）、《商品分类测试用例.md》CGC-API-014～018（sortBy 枚举同源）、《关联需求分析.md》§11（SRH14 展示≠可购）  
> **范围：** C 端 A/B 两页交互；`GET /api/search`；`SearchAppServiceImpl` / `SearchAppController`  
> **代码落位：** `baqing-shop/src/main/java/com/ruoyi/web/modules/home/`（**已实现**）  
> **排除：** 搜索历史服务端、店铺内搜索、联想/热搜、search_keyword 字段检索、加购/下单四条件校验  
> **环境：** RuoYi v3.9.2-springboot2；MySQL 5.7.39；C 端 **无需 Token**（`@Anonymous`）；UI 使用 Playwright **`channel: 'chrome'`**

---

## 用例编号说明

| 前缀 | 类型 | 工具 |
|------|------|------|
| CSR-UT-xxx | 单元测试 | JUnit 5 + Mockito |
| CSR-API-xxx | 接口测试 | MockMvc / Apifox |
| CSR-UI-xxx | 界面测试 | Playwright（Google Chrome） |

> **命名：** **CSR** = Consumer Search（C 端全站搜索）；与商家 keyword 检索区分。

---

## 测试数据约定

| 实体 | 说明 |
|------|------|
| 关键词 | **K1**=`阿莫`（命中商品+店铺）；**K0**=`  `（仅空格）；**KZ**=`不存在xyz999`（无匹配） |
| 店铺 | **S101**「阿莫兽药店」开业 `create_time` 早；**S102**「阿莫饲料行」**停业** `create_time` 晚；**S103** 已删 |
| 出售中商品 | **G-AMO**「阿莫西林」@S101 销量100 价88；**G-AMO2**「阿莫胶囊」@S101 销量100 价99（同销量测次序）；**G-AMO3** 销量50；**G-S102** @S102 停业店；**G-ZERO** stock=0；**G-HIDE** 分类隐藏 |
| 不进结果 | **G-OFF** 已下架；**G-WAIT** 待审核；**G-DEL** 已删；**G-S103** 所属店 S103 已删 |
| 会员 | **M1** 正常；**M2** **禁用**（搜索不受影响） |
| 历史 | localStorage key 以前台约定为准；上限 **20** 条 |

---

## 一、单元测试

### CSR-UT-001 空 keyword 拒绝

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-001 |
| **测试模块** | C 端搜索 |
| **测试项** | keyword 必填 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SRH3 |
| **前置条件** | query.keyword 为空或 null |
| **测试步骤** | 调用 `search(query)` |
| **预期结果** | 抛 `ServiceException`；msg 含「请输入搜索内容」 |

### CSR-UT-002 纯空格 keyword 拒绝

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-002 |
| **测试模块** | C 端搜索 |
| **测试项** | keyword trim |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §4.2 空白拦截 |
| **前置条件** | keyword=`"   "` |
| **测试步骤** | `search(query)` |
| **预期结果** | 抛 `ServiceException`；**不** 调 Mapper |

### CSR-UT-003 keyword trim 后检索

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-003 |
| **测试模块** | C 端搜索 |
| **测试项** | keyword trim |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §4.2 首尾空格 |
| **前置条件** | keyword=`" 阿莫 "`；Mapper mock 返回空 |
| **测试步骤** | `search(query)` |
| **预期结果** | 两次 Mapper 入参 keyword=`阿莫` |

### CSR-UT-004 默认 sortBy 销量降序

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-004 |
| **测试模块** | C 端搜索 |
| **测试项** | sortBy 默认 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SRH9、§5.2 默认 Tab |
| **前置条件** | keyword=K1；sortBy 未传 |
| **测试步骤** | `search(query)` |
| **预期结果** | 商品 Mapper 入参 `sortBy=sales_desc` |

### CSR-UT-005 非法 sortBy 拒绝

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-005 |
| **测试模块** | C 端搜索 |
| **测试项** | sortBy 校验 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §3.1 校验 |
| **前置条件** | keyword=K1；sortBy=`invalid` |
| **测试步骤** | `search(query)` |
| **预期结果** | 抛 `ServiceException`；「排序参数无效」 |

### CSR-UT-006 一次调用双路 Mapper

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-006 |
| **测试模块** | C 端搜索 |
| **测试项** | 统一搜索 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §4 Service 双 SQL |
| **前置条件** | keyword=K1；Mock 商品/店铺各 1 条 |
| **测试步骤** | `search(query)` |
| **预期结果** | 调 `selectGoodsByKeyword` 与 `selectShopsByKeyword` 各 1 次；返回 VO 含 `goods`、`shops` |

### CSR-UT-007 聚合 VO 结构

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-007 |
| **测试模块** | C 端搜索 |
| **测试项** | SearchResultAppVO |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §3.1 响应契约 |
| **前置条件** | 商品 total=2 rows=2；店铺 total=1 rows=1 |
| **测试步骤** | `search(query)` |
| **预期结果** | `goods.total=2`、`shops.total=1`；rows 非 null |

### CSR-UT-008 商品路空、店铺路有数据

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-008 |
| **测试模块** | C 端搜索 |
| **测试项** | 分路空态 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §8.1 各 Tab 独立 |
| **前置条件** | 商品 Mapper 空；店铺 Mapper 1 条 |
| **测试步骤** | `search(query)` |
| **预期结果** | `goods.total=0`；`shops.total=1`；**不** 抛异常 |

### CSR-UT-009 price_asc 透传

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-009 |
| **测试模块** | C 端搜索 |
| **测试项** | sortBy price_asc |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SRH9、§5.3 |
| **前置条件** | sortBy=`price_asc` |
| **测试步骤** | `search(query)` |
| **预期结果** | 商品 Mapper 入参 `price_asc` |

### CSR-UT-010 price_desc 透传

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-010 |
| **测试模块** | C 端搜索 |
| **测试项** | sortBy price_desc |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SRH9 |
| **前置条件** | sortBy=`price_desc` |
| **测试步骤** | `search(query)` |
| **预期结果** | 商品 Mapper 入参 `price_desc` |

### CSR-UT-011 双路分页参数独立

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-011 |
| **测试模块** | C 端搜索 |
| **测试项** | 分页参数 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 §3.1 双路分页 |
| **前置条件** | goodsPageNum=2 goodsPageSize=10；shopPageNum=3 shopPageSize=5 |
| **测试步骤** | `search(query)`（或 Controller 层测 PageHelper） |
| **预期结果** | 商品、店铺分页 **各自** 生效；互不影响 |

### CSR-UT-012 不调用可购 Facade

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UT-012 |
| **测试模块** | C 端搜索 |
| **测试项** | Facade 边界 |
| **测试类型** | 单元测试 |
| **测试工具** | JUnit 5 + Mockito |
| **测试目的** | 验证 SRH14 |
| **前置条件** | 注入 Mock `IGoodsPurchaseFacade` |
| **测试步骤** | `search(query)` |
| **预期结果** | **未** 调用 `canPurchase` / `isCategoryVisible` / `isShopOpenForOrder` |

---

## 二、接口测试

### CSR-API-001 匿名访问无需 Token

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-001 |
| **测试模块** | C 端搜索 |
| **测试项** | GET /api/search 匿名 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc / Apifox |
| **测试目的** | 验证 §1 鉴权 |
| **前置条件** | 不传 Authorization |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | `code=200` |

### CSR-API-002 响应含 goods 与 shops

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-002 |
| **测试模块** | C 端搜索 |
| **测试项** | 聚合契约 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §3.1 统一搜索 |
| **前置条件** | 库中有 K1 命中数据 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | `data.goods`、`data.shops` 均存在；各含 `total`、`rows` |

### CSR-API-003 缺 keyword 失败

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-003 |
| **测试模块** | C 端搜索 |
| **测试项** | keyword 必填 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 SRH3 |
| **前置条件** | 无 |
| **测试步骤** | GET `/api/search`（无 keyword） |
| **预期结果** | 业务失败；msg 含「请输入搜索内容」 |

### CSR-API-004 空格 keyword 失败

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-004 |
| **测试模块** | C 端搜索 |
| **测试项** | keyword trim |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §4.2 |
| **前置条件** | 无 |
| **测试步骤** | GET `/api/search?keyword=%20%20%20` |
| **预期结果** | 业务失败；**不** 返回 200 空结果 |

### CSR-API-005 非法 sortBy 失败

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-005 |
| **测试模块** | C 端搜索 |
| **测试项** | sortBy 校验 |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §3.1 |
| **前置条件** | 无 |
| **测试步骤** | GET `/api/search?keyword=阿莫&sortBy=foo` |
| **预期结果** | 业务失败；「排序参数无效」 |

### CSR-API-006 默认销量降序

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-006 |
| **测试模块** | C 端搜索 |
| **测试项** | sales_desc |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH9 |
| **前置条件** | G-AMO 销量100；G-AMO3 销量50 均名含「阿莫」 |
| **测试步骤** | GET `/api/search?keyword=阿莫`（不传 sortBy） |
| **预期结果** | `goods.rows` 中 G-AMO 系列在 G-AMO3 前 |

### CSR-API-007 价格升序

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-007 |
| **测试模块** | C 端搜索 |
| **测试项** | price_asc |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH9、§5.3 |
| **前置条件** | G-AMO 价88；G-AMO2 价99 |
| **测试步骤** | GET `/api/search?keyword=阿莫&sortBy=price_asc` |
| **预期结果** | 88 元商品在前 |

### CSR-API-008 价格降序

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-008 |
| **测试模块** | C 端搜索 |
| **测试项** | price_desc |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH9 |
| **前置条件** | 同 API-007 |
| **测试步骤** | GET `...&sortBy=price_desc` |
| **预期结果** | 99 元商品在前 |

### CSR-API-009 同价按 goodsSn 次序

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-009 |
| **测试模块** | C 端搜索 |
| **测试项** | 次要排序键 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §5.3 |
| **前置条件** | G-AMO、G-AMO2 同销量100；sn 不同 |
| **测试步骤** | GET `...&sortBy=sales_desc` |
| **预期结果** | 同销量时 `goods_sn` 升序稳定 |

### CSR-API-010 仅出售中商品

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-010 |
| **测试模块** | C 端搜索 |
| **测试项** | goods_status |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH6 |
| **前置条件** | G-OFF、G-WAIT 名称含 K1；G-AMO 出售中 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | `goods.rows` **仅** 含出售中；**不含** G-OFF/G-WAIT |

### CSR-API-011 已删商品不出现

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-011 |
| **测试模块** | C 端搜索 |
| **测试项** | del_flag 商品 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH6 |
| **前置条件** | G-DEL 名称含 K1 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | **不含** G-DEL |

### CSR-API-012 停业店商品可出现

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-012 |
| **测试模块** | C 端搜索 |
| **测试项** | 浏览层店态 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH7 |
| **前置条件** | G-S102 @S102 停业、出售中 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | **含** G-S102 |

### CSR-API-013 零库存商品可出现

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-013 |
| **测试模块** | C 端搜索 |
| **测试项** | 浏览层库存 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH7、§10 |
| **前置条件** | G-ZERO stock=0 名称含 K1 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | **含** G-ZERO |

### CSR-API-014 分类隐藏商品可出现

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-014 |
| **测试模块** | C 端搜索 |
| **测试项** | 浏览层分类 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH7、§1.3 C-P5 |
| **前置条件** | G-HIDE 所属分类 show=0 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | **含** G-HIDE |

### CSR-API-015 已删店铺商品不出现

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-015 |
| **测试模块** | C 端搜索 |
| **测试项** | 店已删 JOIN |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §6.1 店未删 |
| **前置条件** | G-S103 @S103 已删 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | **不含** G-S103 |

### CSR-API-016 店铺 Tab 店名模糊

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-016 |
| **测试模块** | C 端搜索 |
| **测试项** | shop_name LIKE |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH10 |
| **前置条件** | S101 店名含「阿莫」 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | `shops.rows` 含 S101 |

### CSR-API-017 停业店铺可出现

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-017 |
| **测试模块** | C 端搜索 |
| **测试项** | 店铺浏览层 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §6.2 |
| **前置条件** | S102 停业、店名含 K1 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | `shops.rows` **含** S102 |

### CSR-API-018 已删店铺不出现

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-018 |
| **测试模块** | C 端搜索 |
| **测试项** | shop del_flag |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §6.2 |
| **前置条件** | S103 已删、原名含 K1 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | **不含** S103 |

### CSR-API-019 店铺创建时间升序

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-019 |
| **测试模块** | C 端搜索 |
| **测试项** | 店铺排序 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH10、§5.4 |
| **前置条件** | S101 早于 S102；均名含 K1 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | S101 在 S102 前 |

### CSR-API-020 商品 VO 字段契约

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-020 |
| **测试模块** | C 端搜索 |
| **测试项** | HomeHotGoodsVO |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §7.1 |
| **前置条件** | 有命中商品 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | rows 含 `goodsId/goodsName/mainPic/salePrice/shopName`；**不含** 库存/销量展示字段 |

### CSR-API-021 店铺 VO 字段契约

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-021 |
| **测试模块** | C 端搜索 |
| **测试项** | SearchShopAppVO |
| **测试类型** | 接口测试 |
| **测试工具** | MockMvc |
| **测试目的** | 验证 §7.2 |
| **前置条件** | 有命中店铺 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | rows 含 `shopId/shopName/shopAvatar`；rating/fansCount 可为 null |

### CSR-API-022 商品分页

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-022 |
| **测试模块** | C 端搜索 |
| **测试项** | goodsPageNum |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §6.1 分页 |
| **前置条件** | K1 命中商品 ≥15 条；pageSize=10 |
| **测试步骤** | ① page1 ② `goodsPageNum=2` |
| **预期结果** | total 一致；两页 rows **不重复** |

### CSR-API-023 店铺分页

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-023 |
| **测试模块** | C 端搜索 |
| **测试项** | shopPageNum |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §6.2 分页 |
| **前置条件** | K1 命中店铺 ≥6 条 |
| **测试步骤** | 递增 `shopPageNum` |
| **预期结果** | 分页正确；与商品分页 **独立** |

### CSR-API-024 商品无匹配店铺有匹配

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-024 |
| **测试模块** | C 端搜索 |
| **测试项** | 分路空态 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 §8.1 |
| **前置条件** | 仅 S101 店名含 KZ；无商品名含 KZ |
| **测试步骤** | GET `/api/search?keyword=不存在xyz999` |
| **预期结果** | `goods.total=0`；`shops.total≥0`（若有店名误命中则为 0） |

### CSR-API-025 双路均无匹配

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-025 |
| **测试模块** | C 端搜索 |
| **测试项** | 全空 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH13 |
| **前置条件** | KZ 无商品无店铺 |
| **测试步骤** | GET `/api/search?keyword=不存在xyz999` |
| **预期结果** | `code=200`；`goods.total=0`；`shops.total=0` |

### CSR-API-026 全站无 shopId 限定

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-API-026 |
| **测试模块** | C 端搜索 |
| **测试项** | SRH1 全站 |
| **测试类型** | 接口测试 |
| **测试工具** | Apifox |
| **测试目的** | 验证 SRH1 |
| **前置条件** | 多店铺均有 K1 商品 |
| **测试步骤** | GET `/api/search?keyword=阿莫` |
| **预期结果** | 返回 **跨店** 商品；接口 **无** shopId 参数 |

---

## 三、界面测试

### CSR-UI-001 首页搜索栏进 A 页

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-001 |
| **测试模块** | C 端搜索 |
| **测试项** | 首页入口 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH2 |
| **前置条件** | 打开商城首页 |
| **测试步骤** | 点击顶栏搜索栏 |
| **预期结果** | 进入 **A. 搜索输入页** |

### CSR-UI-002 分类页搜索栏进 A 页

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-002 |
| **测试模块** | C 端搜索 |
| **测试项** | 分类入口 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH2 |
| **前置条件** | 打开商品分类页 |
| **测试步骤** | 点击搜索栏 |
| **预期结果** | 进入 A 页 |

### CSR-UI-003 搜索占位文案

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-003 |
| **测试模块** | C 端搜索 |
| **测试项** | placeholder |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH15 |
| **前置条件** | A 页或首页搜索栏 |
| **测试步骤** | 读取 placeholder |
| **预期结果** | **「搜索兽药、饲料、店铺」** |

### CSR-UI-004 空关键词不跳转

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-004 |
| **测试模块** | C 端搜索 |
| **测试项** | 空搜拦截 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH3 |
| **前置条件** | A 页；输入框为空 |
| **测试步骤** | 点击「搜索」或回车 |
| **预期结果** | **停留** A 页；Toast「请输入搜索内容」类提示 |

### CSR-UI-005 纯空格不跳转

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-005 |
| **测试模块** | C 端搜索 |
| **测试项** | 空格拦截 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §4.2 |
| **前置条件** | A 页 |
| **测试步骤** | 输入空格 → 搜索 |
| **预期结果** | **不** 进 B 页；**不** 写历史 |

### CSR-UI-006 非空搜索进 B 页

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-006 |
| **测试模块** | C 端搜索 |
| **测试项** | A→B 流程 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §4.2、§9 |
| **前置条件** | A 页 |
| **测试步骤** | 输入 K1 → 点击搜索 |
| **预期结果** | 进入 B 页；顶栏展示关键词 K1 |

### CSR-UI-007 搜索历史写入与倒序

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-007 |
| **测试模块** | C 端搜索 |
| **测试项** | 历史写入 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH4 |
| **前置条件** | 清空历史；依次搜「饲料」「阿莫」 |
| **测试步骤** | 返回 A 页查看历史区 |
| **预期结果** | 「阿莫」在「饲料」**上方**（最近优先） |

### CSR-UI-008 历史去重置顶

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-008 |
| **测试模块** | C 端搜索 |
| **测试项** | 历史去重 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §4.3 |
| **前置条件** | 历史含「阿莫」「饲料」 |
| **测试步骤** | 再次搜索「饲料」→ 回 A 页 |
| **预期结果** | 「饲料」**置顶**；**无** 重复条目 |

### CSR-UI-009 单条删除历史

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-009 |
| **测试模块** | C 端搜索 |
| **测试项** | 单删 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH4 |
| **前置条件** | 历史 ≥2 条 |
| **测试步骤** | 删除其中一条 |
| **预期结果** | 该条消失；其余保留 |

### CSR-UI-010 清空全部历史

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-010 |
| **测试模块** | C 端搜索 |
| **测试项** | 全部清空 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH4 |
| **前置条件** | 历史 ≥1 条 |
| **测试步骤** | 点击「清空」 |
| **预期结果** | 历史区空或「暂无搜索历史」 |

### CSR-UI-011 B 页默认按销量 Tab

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-011 |
| **测试模块** | C 端搜索 |
| **测试项** | 默认 Tab |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §5.2 |
| **前置条件** | 首次从 A 搜 K1 进 B |
| **测试步骤** | 观察 Tab 与列表 |
| **预期结果** | **「按销量」** 高亮；展示 **商品** 列表 |

### CSR-UI-012 切换按价格默认低到高

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-012 |
| **测试模块** | C 端搜索 |
| **测试项** | 按价格 Tab |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §5.3 |
| **前置条件** | B 页 K1 有商品 |
| **测试步骤** | 点击「按价格」Tab |
| **预期结果** | 仍展示商品；**价格从低到高**；请求含 `sortBy=price_asc` |

### CSR-UI-013 按价格升/降切换

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-013 |
| **测试模块** | C 端搜索 |
| **测试项** | 价序切换 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH9 |
| **前置条件** | 在「按价格」Tab |
| **测试步骤** | 切换升/降序控件 |
| **预期结果** | 列表顺序反转；请求 `price_asc` ↔ `price_desc` |

### CSR-UI-014 切换店铺 Tab

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-014 |
| **测试模块** | C 端搜索 |
| **测试项** | 店铺 Tab |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH8、§5.2 |
| **前置条件** | B 页已搜 K1 |
| **测试步骤** | 点击「店铺」Tab |
| **预期结果** | 展示 **店铺** 列表；**不** 展示商品卡片 |

### CSR-UI-015 Tab 互斥仅一高亮

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-015 |
| **测试模块** | C 端搜索 |
| **测试项** | Tab 互斥 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH8 |
| **前置条件** | B 页 |
| **测试步骤** | 依次点三个 Tab |
| **预期结果** | 任意时刻 **仅一个** Tab 高亮 |

### CSR-UI-016 重新搜索回 A 并预填

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-016 |
| **测试模块** | C 端搜索 |
| **测试项** | 重新搜索 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH5 |
| **前置条件** | B 页关键词 K1 |
| **测试步骤** | 点击顶栏搜索条 |
| **预期结果** | 进 A 页；输入框 **预填** K1 |

### CSR-UI-017 修改关键词刷新结果

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-017 |
| **测试模块** | C 端搜索 |
| **测试项** | 换词搜索 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §6.3 |
| **前置条件** | B 页 K1 |
| **测试步骤** | 重新搜索区 → A 页改 KZ → 搜索 |
| **预期结果** | B 页关键词与结果 **更新** 为 KZ |

### CSR-UI-018 商品卡片字段展示

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-018 |
| **测试模块** | C 端搜索 |
| **测试项** | 商品卡片 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §7.1 |
| **前置条件** | B 页商品 Tab 有结果 |
| **测试步骤** | 检查首条卡片 |
| **预期结果** | 含主图、名称、售价、店名；**不含** 库存/销量数字 |

### CSR-UI-019 店铺卡片字段展示

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-019 |
| **测试模块** | C 端搜索 |
| **测试项** | 店铺卡片 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §7.2 |
| **前置条件** | B 页店铺 Tab 有结果 |
| **测试步骤** | 检查首条卡片 |
| **预期结果** | 含头像、店名；评分/粉丝 **隐藏或占位** |

### CSR-UI-020 点击商品进详情

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-020 |
| **测试模块** | C 端搜索 |
| **测试项** | 进详情 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH11 |
| **前置条件** | 商品列表有数据 |
| **测试步骤** | 点击商品卡片 |
| **预期结果** | 进入商品详情；URL/路由含 `goodsId` |

### CSR-UI-021 点击店铺进店铺主页

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-021 |
| **测试模块** | C 端搜索 |
| **测试项** | 进店铺主页 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH12 |
| **前置条件** | 店铺列表有数据；店铺主页已部署 |
| **测试步骤** | 点击店铺卡片 |
| **预期结果** | 进入店铺主页；含 `shopId` |

### CSR-UI-022 商品 Tab 空态暂无相关

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-022 |
| **测试模块** | C 端搜索 |
| **测试项** | 商品空态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 SRH13 |
| **前置条件** | KZ 无商品；店铺 Tab 可有数据 |
| **测试步骤** | 搜 KZ → 停留商品 Tab |
| **预期结果** | 展示 **「暂无相关」** |

### CSR-UI-023 店铺 Tab 独立空态

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-023 |
| **测试模块** | C 端搜索 |
| **测试项** | 店铺空态 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §8.1 |
| **前置条件** | 仅商品命中 K1、无店名命中（或反向造数） |
| **测试步骤** | 切「店铺」Tab |
| **预期结果** | 店铺区「暂无相关」；**不影响** 商品 Tab 有数据 |

### CSR-UI-024 访客与会员均可搜索

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-024 |
| **测试模块** | C 端搜索 |
| **测试项** | 匿名/会员 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §1.4 |
| **前置条件** | ① 未登录 ② M1 登录 ③ M2 禁用登录 |
| **测试步骤** | 分别完成 A→B 搜索 |
| **预期结果** | 三种身份 **均可** 浏览结果 |

### CSR-UI-025 列表加载失败可重试

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-025 |
| **测试模块** | C 端搜索 |
| **测试项** | 接口失败 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §8.2 |
| **前置条件** | Mock `/api/search` 500 |
| **测试步骤** | 进 B 页 |
| **预期结果** | 当前 Tab **错误/空态**；**不** 整页白屏；可重试 |

### CSR-UI-026 历史读取失败不阻塞搜索

| 要素 | 内容 |
|------|------|
| **用例编号** | CSR-UI-026 |
| **测试模块** | C 端搜索 |
| **测试项** | 历史异常 |
| **测试类型** | 界面测试 |
| **测试工具** | Playwright（Chrome） |
| **测试目的** | 验证 §8.2 |
| **前置条件** | Mock localStorage 读异常或清空权限 |
| **测试步骤** | 打开 A 页并搜索 |
| **预期结果** | 历史区不展示或空；**仍可** 输入并搜索 |

---

## 四、业务规则覆盖矩阵

| 规则 | UT | API | UI |
|------|:--:|:---:|:--:|
| SRH1 全站 | — | API-026 | — |
| SRH2 入口 | — | — | UI-001/002 |
| SRH3 空关键词 | UT-001/002 | API-003/004 | UI-004/005 |
| SRH4 历史 | — | — | UI-007～010/026 |
| SRH5 重新搜索 | — | — | UI-016/017 |
| SRH6 仅出售中 | — | API-010/011 | — |
| SRH7 浏览层 | UT-012 | API-012～015 | — |
| SRH8 三 Tab | — | — | UI-011/014/015 |
| SRH9 排序 | UT-004/009/010 | API-006～009 | UI-011～013 |
| SRH10 店铺排序 | — | API-016/019 | — |
| SRH11 进详情 | — | — | UI-020 |
| SRH12 进店铺 | — | — | UI-021 |
| SRH13 暂无相关 | UT-008 | API-025 | UI-022/023 |
| SRH14 展示≠可购 | UT-012 | — | — |
| SRH15 占位 | — | — | UI-003 |
| 统一搜索双路 | UT-006/007 | API-002 | UI-006 |
| 双路独立空态 | UT-008 | API-024/025 | UI-022/023 |
| 分页 | UT-011 | API-022/023 | — |

---

## 五、自动化映射

| 用例 | 建议代码位置 |
|------|--------------|
| CSR-UT-001～012 | `SearchAppServiceImplTest.java` |
| CSR-API-001～026 | `SearchAppControllerTest.java` + Apifox 集合 |
| CSR-UI-001～026 | `e2e/consumer/search.spec.ts`（Playwright `channel: 'chrome'`） |

**Playwright 示例配置：**

```typescript
// playwright.config.ts 片段
use: { channel: 'chrome', baseURL: process.env.C_APP_URL }
```

**UI 历史类用例：** 通过 `page.evaluate` 读写 localStorage，或 `context.storageState` 隔离。

---

## 六、用例统计

| 类型 | 数量 |
|------|------|
| 单元测试 CSR-UT | 12 |
| 接口测试 CSR-API | 26 |
| 界面测试 CSR-UI | 26 |
| **合计** | **64** |

---

## 七、修订记录

| 版本 | 说明 |
|------|------|
| **v1.0** | 首版；64 用例；覆盖 SRH1～SRH15、A/B 两页、`GET /api/search` 统一双路搜索 |

---

*文档版本：v1.0 · 关联《搜索页功能需求.md》v1.0、《搜索页技术方案.md》v1.1*
