# 固定地点签到（Site Attendance）- E2E 测试编排

> **module**: site-attendance
> **doc_type**: E2ETestSpec
> **status**: Draft
> **owner**: 待定
> **upstream_docs**: `01-prd.md`, `05-ui-interaction-spec.md`, `09-test-scenarios.md`
> **last_verified**: 2026-04-17

---

## 执行方式

- 通过 **Playwright MCP** + AI 执行，不编写 E2E 测试代码
- 元素定位使用 accessibility tree（role / name），不依赖 `data-testid`
- 关闭 mock，走真实后端 + 测试数据库
- 自动化跑核心链路，UAT 部署后逐按钮全量人工复测（见 `feedback_uat_full_verification_before_prod`）

---

## 测试前置条件

| 项 | 设置 |
|----|------|
| 两套 workspace | FF workspace + AIxC workspace（本机用 port 3000 / 3010 模拟，或真实 UAT 环境） |
| 环境变量 | `SHARED_CHECKIN_SECRET` 两侧同值；`SHARED_CHECKIN_ALLOWED_HOSTS` 两侧白名单互加 |
| 测试账号 | 每侧准备 Administrator 账号 + 普通员工账号（各 2 人） |
| checkpoint 数据 | FF 侧 `FF-LOBBY`；AIxC 侧 `AIXC-HQ` |
| Partner 配置 | FF-LOBBY 挂 AIXC-HQ 作为 Partner |

---

## E2E 场景列表

### FLOW-001：形态 A（PUBLIC + 不轮换）完整签到

**目标**：确认 v1.5 上线后 v1.0 行为零破坏

**步骤**:
1. Admin 登录 FF workspace
2. 新建 checkpoint：`accessMode=PUBLIC, qrRotationSeconds=null, sharedCheckinEnabled=false`
3. 在详情页复制独立签到页 URL
4. 在新窗口粘贴 URL（不带任何 token）→ 期望看到签到页
5. 登录员工账号 → 签到 → 期望 201 + 事件出现在详情页列表
6. 点击"签退" → 期望 201

**通过标准**: v1.0 链路不受影响

---

### FLOW-002：形态 B（SIGNED + 轮换）独立大屏签到

**步骤**:
1. Admin 把 FF-LOBBY 改为 `accessMode=SIGNED, qrRotationSeconds=3600`
2. 详情页复制大屏地址 → 在大屏浏览器打开
3. 期望：大屏渲染 QR，QR URL 带 `?t=<bucket>.<mac>`
4. 用手机扫 QR → 跳转签到页
5. 登录员工 → 签到 → 期望 201
6. 直接访问签到页不带 token：`/siteattendance/c/FF-LOBBY` → 期望看到"请通过前台二维码签到"错误页

**通过标准**: 合法路径能签到，绕过 QR 直连被拒

---

### FLOW-003：形态 C（共享分诊）跨域跳转

**步骤**:
1. Admin 把 FF-LOBBY 改为 `sharedCheckinEnabled=true, sharedCompanyId='ff', sharedCompanyLabel='Faraday Future'`
2. 添加 Partner：`companyId='aixc', companyLabel='AIxCrypto', targetUrl=<AIxC workspace checkpoint URL>`
3. 详情页复制共享分诊页 URL（来自新的"访问地址"区块）
4. 大屏打开共享分诊页 → 渲染分诊 QR
5. 手机扫 QR → 进入分诊页 → 展示 `Faraday Future` + `AIxCrypto` 卡片
6. 点击 `Faraday Future` → 跳转 FF 签到页（本域）
7. 签到成功
8. 回到分诊页重新扫码，点击 `AIxCrypto` → 跨域跳转到 AIxC workspace 的签到页
9. AIxC 侧登录 → 签到 → 期望 201
10. 查 AIxC DB：`SharedCheckinTicketUsage` 有 nonce 记录

**通过标准**: 跨域 ticket 签发 + 跨域校验通过 + 签到事件写入正确的 workspace

---

### FLOW-004：localStorage 记忆 + 切换公司

**步骤**:
1. 基于 FLOW-003 的配置，第二次扫 FF 的共享 QR
2. 期望：**不再展示选择页，直接跳到上次选的公司**（localStorage 命中）
3. 在签到页点击右上角"切换公司"按钮
4. 期望：跳回分诊页，展示选择列表（localStorage 已清除）
5. 选不同公司 → 完整签到

**通过标准**: localStorage 按 checkpoint code 分区工作，切换正常

---

### FLOW-005：旧 QR 失效提示

**步骤**:
1. 形态 B 环境下，截图一张当前的 QR
2. 等到下一个整点 + 3 分钟（超过宽限期）
3. 扫旧截图的 QR
4. 期望：显示"二维码已过期，请扫描前台最新二维码"

**通过标准**: 旧 token 真实失效

---

### FLOW-006：Ticket 重放拒绝

**步骤**:
1. 通过分诊拿到 redirectUrl（含 ticket）
2. 复制 URL，第一次在浏览器打开 → 签到成功
3. 回退 / 重开同一 URL 再次打开
4. 期望：显示"签到链接已被使用，请重新扫码"

**通过标准**: nonce 一次性去重生效

---

### FLOW-007：Partner URL 白名单保护（管理端）

**步骤**:
1. Admin 在 FF-LOBBY 添加 Partner
2. 填 targetUrl = `https://evil.example.com/xxx`（不在白名单）
3. 点击保存
4. 期望：显示错误"目标地址不在白名单内，请联系运维添加"

**通过标准**: 白名单保存前拦截

---

### FLOW-008：形态 D（独立 + 共享并存）

**步骤**:
1. FF-LOBBY 配置为 `accessMode=SIGNED, qrRotationSeconds=3600, sharedCheckinEnabled=true`
2. 详情页同时显示：独立大屏 URL + 共享分诊 URL
3. 打开独立大屏 → 签到链路走 [t] 分支
4. 打开共享分诊大屏 → 扫码进分诊 → 走 [ticket] 分支
5. 两种路径都能成功签到

**通过标准**: 同一 checkpoint 并行支持两种入口，数据都写同一个 AttendanceEvent 表

---

### FLOW-009：大屏整点对齐刷新

**步骤**:
1. 形态 B 大屏打开
2. 观察 QR 刷新时机
3. 期望：整点（如 10:00:00）后 1 秒 QR 变化（新 bucket）
4. 扫新 QR 能正常签到

**通过标准**: 大屏跟服务端 bucket 同步，无漂移

---

### FLOW-010：免登录签到（guest）+ ticket

**步骤**:
1. 共享分诊后到达 FF 签到页（带 ticket）
2. 不登录，直接点签到
3. 弹窗显示"登录后继续 / 免登录继续"
4. 选择免登录 → 搜索用户 → 选中 → 签到
5. 期望：201，事件 `authMethod=UNAUTHENTICATED`

**通过标准**: guest 流程与 ticket 能共存

---

### FLOW-011：errorPage 全局降级体验

**目标**：所有 v1.5 新增错误态都有清晰用户提示，不白屏

**步骤**:
1. 依次触发以下错误，逐一截图 + 验证文案：
   - QR_TOKEN_EXPIRED
   - QR_TOKEN_INVALID
   - QR_TOKEN_MISSING
   - TICKET_EXPIRED
   - TICKET_ALREADY_USED
   - TICKET_TARGET_MISMATCH
   - DISPATCH_NOT_ENABLED
2. 每种错误应显示：居中错误卡片 + 清晰标题 + 副标题 + 合理的按钮（返回首页 / 重试 / 返回分诊页）

**通过标准**: 零白屏；文案与 `08-error-codes.md` 一致

---

## 跑测节奏

### 自动化（Playwright MCP）

- **PR 合并前**：FLOW-001, FLOW-002, FLOW-003, FLOW-004, FLOW-011 基础 smoke
- **UAT 部署后**：FLOW-001 ~ FLOW-011 全量

### 人工 L3

- UAT 部署后 AI 全链路跑完 + 再由用户本人在 UAT 前端手动点一遍
- 特别关注视觉细节、移动端样式、刷新节奏（整点附近）

---

## 测试数据清理

- 共享 secret / 白名单等 env 变量由运维维护，测试前检查
- 每次 E2E 后清理：
  - 新建的 checkpoint（可选，测试 DB force-reset 会清）
  - `SharedCheckinTicketUsage` 记录
  - `SharedCheckinPartner` 记录

---

## 测试报告输出

每次 E2E 跑完输出到 `testing/reports/site-attendance-shared-checkin-YYYYMMDD.md`：
- 每个 FLOW 的实际路径、截图、通过/失败、耗时
- 发现的问题 + 修复 commit
- 形态覆盖矩阵（A/B/C/D × 登录/guest = 8 格必须全绿）
