R104/A1-A7 完成公网部署与视觉验收
这个提交包含在:
@@ -0,0 +1,198 @@
|
|||||||
|
# R104 公网部署与视觉验收报告
|
||||||
|
|
||||||
|
日期:2026-05-07
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
R100-R103 最新变更已部署到公网 `https://oa.hyinda.com`,自动化验收通过。
|
||||||
|
|
||||||
|
已完成:
|
||||||
|
|
||||||
|
- 本地发布前门禁通过。
|
||||||
|
- 已创建发布 tag:`trialrun-20260507-01`。
|
||||||
|
- 已执行 `bash deploy/trialrun/deploy.sh` 部署到 `118.89.77.218:/opt/hyb-oa-trialrun`。
|
||||||
|
- 公网 `/ready` 通过。
|
||||||
|
- 远端 `smoke-external-deps.js` 通过,当前 OCR provider 为 `tencent`。
|
||||||
|
- 远端 `smoke-trialrun-runtime.js` 通过。
|
||||||
|
- 公网 live smoke 通过:管理员、员工只读 smoke 共 3 条通过。
|
||||||
|
- 公网视觉 Playwright 通过:桌面登录、企微 OAuth 边界、移动工作台、移动报销/请假、PC 报销/票据共 4 条通过。
|
||||||
|
- 公网真实腾讯 OCR 辅助复核通过:真实 PDF 发票上传后进入可处理状态。
|
||||||
|
|
||||||
|
仍需人工确认:
|
||||||
|
|
||||||
|
- 用真实手机企业微信从工作台打开 OA 应用,确认已绑定账号直接进入 `/mobile`。自动化已覆盖到 OAuth 跳转边界,但不能替代真实企业微信客户端内的最终点击。
|
||||||
|
|
||||||
|
## 本地发布前验证
|
||||||
|
|
||||||
|
已执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node deploy/trialrun/validate-env-contract.js
|
||||||
|
cd server && npm run prisma:validate
|
||||||
|
node scripts/qa/check-ui-design-rules.mjs
|
||||||
|
cd client && npm run typecheck
|
||||||
|
cd client && npm run build
|
||||||
|
cd client && npx playwright test tests/e2e/r102-reimbursement-main-flow.spec.ts tests/e2e/r103-cross-page-ui-consistency.spec.ts --reporter=list
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
- 部署环境契约:通过。
|
||||||
|
- Prisma schema:通过。
|
||||||
|
- UI 设计规则:`0 findings`。
|
||||||
|
- 前端 typecheck:通过。
|
||||||
|
- 前端 build:通过;仍有既有 Vite manual chunk circular warning,本批不处理。
|
||||||
|
- R102/R103 Playwright:`6 passed`。
|
||||||
|
|
||||||
|
## 公网部署
|
||||||
|
|
||||||
|
已执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag -a trialrun-20260507-01 -m "trialrun release 20260507-01"
|
||||||
|
bash deploy/trialrun/deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
部署结果:
|
||||||
|
|
||||||
|
- 远端目录:`/opt/hyb-oa-trialrun`
|
||||||
|
- 远端环境文件:`/opt/hyb-oa-trialrun/deploy/trialrun/.env`
|
||||||
|
- `.env` 未从本地同步,沿用远端事实源。
|
||||||
|
- 远端 server 容器已重建。
|
||||||
|
- 部署脚本内 `/ready`、external deps smoke 通过。
|
||||||
|
- 部署脚本本机缺少完整 `LIVE_SMOKE_*`,因此 live smoke 由后续步骤手动补跑。
|
||||||
|
|
||||||
|
## 公网配置核验
|
||||||
|
|
||||||
|
已执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsS https://oa.hyinda.com/ready
|
||||||
|
curl -fsS https://oa.hyinda.com/api/auth/wecom/config
|
||||||
|
curl -sSI 'https://oa.hyinda.com/api/auth/wecom/login?returnTo=%2Fmobile'
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
- `/ready`:通过。
|
||||||
|
- 企业微信配置:
|
||||||
|
- `status=ready`
|
||||||
|
- `entryEnabled=true`
|
||||||
|
- `oauthEnabled=true`
|
||||||
|
- `defaultReturnTo=/mobile`
|
||||||
|
- `loginUrl=/api/auth/wecom/login?returnTo=%2Fmobile`
|
||||||
|
- 企业微信登录入口:`HTTP 302` 到 `open.weixin.qq.com`,回调地址为 `https://oa.hyinda.com/api/auth/wecom/callback`,state 中 `returnTo=/mobile`。
|
||||||
|
|
||||||
|
远端关键配置只读核验:
|
||||||
|
|
||||||
|
- `WECOM_ENTRY_ENABLED=true`
|
||||||
|
- `WECOM_OAUTH_ENABLED=true`
|
||||||
|
- `OPENAPI_DOCS_ENABLED=false`
|
||||||
|
- `COOKIE_SECURE=true`
|
||||||
|
- `PUBLIC_SECURITY_HARDENING_ENABLED=true`
|
||||||
|
- `OCR_PROVIDER=tencent`
|
||||||
|
|
||||||
|
## 远端 smoke
|
||||||
|
|
||||||
|
已执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh [email protected] 'cd /opt/hyb-oa-trialrun && ... exec -T server node src/scripts/smoke-trialrun-runtime.js'
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
- Prisma 连接正常。
|
||||||
|
- 工作区 `profile=small_team_internal`。
|
||||||
|
- trialrun 必需功能开关均 enabled。
|
||||||
|
- 导航构建成功:`main=9 admin=7`。
|
||||||
|
- `trialrun runtime smoke` 通过。
|
||||||
|
|
||||||
|
## 公网 live smoke
|
||||||
|
|
||||||
|
操作:
|
||||||
|
|
||||||
|
- 仅重置独立 smoke 账号 `SMOKE001` / `SMOKE002` 随机密码。
|
||||||
|
- 密码仅作为临时环境变量传给 Playwright,未写入 git,未写入报告。
|
||||||
|
|
||||||
|
已执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LIVE_SMOKE_BASE_URL=https://oa.hyinda.com \
|
||||||
|
LIVE_EXPECT_WECOM_STATUS=ready \
|
||||||
|
LIVE_SMOKE_ADMIN_IDENTIFIER=[email protected] \
|
||||||
|
LIVE_SMOKE_EMPLOYEE_IDENTIFIER=[email protected] \
|
||||||
|
bash scripts/qa/run-web-gates.sh smoke-live
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
- 公网基础探活与企微配置状态:通过。
|
||||||
|
- 管理员只读网页 smoke:通过。
|
||||||
|
- 员工只读网页 smoke:通过。
|
||||||
|
- 总计:`3 passed`。
|
||||||
|
|
||||||
|
## 公网视觉自动化
|
||||||
|
|
||||||
|
新增测试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
client/tests/e2e/r104-public-visual-acceptance.spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
已执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd client
|
||||||
|
LIVE_SMOKE_BASE_URL=https://oa.hyinda.com \
|
||||||
|
LIVE_EXPECT_WECOM_STATUS=ready \
|
||||||
|
LIVE_SMOKE_ADMIN_IDENTIFIER=[email protected] \
|
||||||
|
LIVE_SMOKE_EMPLOYEE_IDENTIFIER=[email protected] \
|
||||||
|
E2E_MODE=live \
|
||||||
|
npx playwright test tests/e2e/r104-public-visual-acceptance.spec.ts --reporter=list
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:`4 passed`。
|
||||||
|
|
||||||
|
覆盖:
|
||||||
|
|
||||||
|
- 公网桌面登录页与企业微信配置。
|
||||||
|
- 公网企业微信入口跳转到 OAuth 边界。
|
||||||
|
- 公网员工移动工作台、移动报销、移动请假。
|
||||||
|
- 公网 PC 报销与票据页面。
|
||||||
|
|
||||||
|
截图输出:
|
||||||
|
|
||||||
|
- `output/playwright/r104/desktop-login.png`
|
||||||
|
- `output/playwright/r104/mobile-login-before-wecom.png`
|
||||||
|
- `output/playwright/r104/mobile-home.png`
|
||||||
|
- `output/playwright/r104/mobile-reimbursements.png`
|
||||||
|
- `output/playwright/r104/mobile-leave.png`
|
||||||
|
- `output/playwright/r104/desktop-reimbursements.png`
|
||||||
|
- `output/playwright/r104/desktop-invoice-pool.png`
|
||||||
|
|
||||||
|
## 公网真实腾讯 OCR
|
||||||
|
|
||||||
|
远端 OCR provider 当前为 `tencent`,因此执行真实 PDF 发票辅助复核。
|
||||||
|
|
||||||
|
已执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd client
|
||||||
|
PLAYWRIGHT_MANUAL=1 \
|
||||||
|
PLAYWRIGHT_OCR_LIVE=1 \
|
||||||
|
PLAYWRIGHT_OCR_LIVE_FILE=/home/shunli/Projects/hyb-oa/artifacts/北京悦海堂餐饮管理有限公司_发票金额1683.00元.pdf \
|
||||||
|
E2E_BASE_URL=https://oa.hyinda.com \
|
||||||
|
npx playwright test tests/e2e/manual/live-invoice-ocr.spec.ts --workers=1 --reporter=list
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
- `1 passed`
|
||||||
|
- 链路:真实 PDF 上传 -> 腾讯 OCR -> 进入可处理状态。
|
||||||
|
|
||||||
|
## 风险与后续
|
||||||
|
|
||||||
|
- 真实企业微信客户端内最终落点仍需人工确认。
|
||||||
|
- Vite manual chunk circular warning 仍存在,应单独开技术优化 Issue。
|
||||||
|
- 公网 live OCR 会在 smoke 账号下产生测试票据记录;后续如需要,可开清理或测试数据归档专项。
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { mkdirSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { expect, test, type Page } from "@playwright/test";
|
||||||
|
|
||||||
|
import { getExpectedWecomStatus, loginLiveUser, logoutLiveUser } from "./helpers/liveAuth";
|
||||||
|
import { fetchWecomConfig } from "./helpers/smokeAssertions";
|
||||||
|
|
||||||
|
const screenshotDir = path.resolve(process.cwd(), "../output/playwright/r104");
|
||||||
|
|
||||||
|
function screenshotPath(fileName: string) {
|
||||||
|
mkdirSync(screenshotDir, { recursive: true });
|
||||||
|
return path.join(screenshotDir, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectNoDesignRegressionCopy(page: Page) {
|
||||||
|
const bodyText = await page.locator("body").innerText();
|
||||||
|
expect(bodyText).not.toContain("先处理这几件事");
|
||||||
|
expect(bodyText).not.toContain("清爽");
|
||||||
|
expect(bodyText).not.toContain("/invoice-pool");
|
||||||
|
expect(bodyText).not.toContain("/purchase-requests");
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe("R104 public visual acceptance", () => {
|
||||||
|
test.skip(process.env.E2E_MODE !== "live", "仅在公网 live 模式下执行");
|
||||||
|
|
||||||
|
test("公网桌面登录页与企业微信配置可见", async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 1440, height: 960 });
|
||||||
|
await page.goto("/login");
|
||||||
|
|
||||||
|
await expect(page.getByTestId("login-identifier")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("login-password")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("login-submit")).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: /企业微信/ })).toBeVisible();
|
||||||
|
|
||||||
|
const wecomConfig = await fetchWecomConfig(page);
|
||||||
|
expect(wecomConfig.status).toBe(200);
|
||||||
|
expect(wecomConfig.data?.status).toBe(getExpectedWecomStatus());
|
||||||
|
expect(wecomConfig.data?.defaultReturnTo).toBe("/mobile");
|
||||||
|
expect(wecomConfig.data?.loginUrl).toContain("returnTo=%2Fmobile");
|
||||||
|
|
||||||
|
await expectNoDesignRegressionCopy(page);
|
||||||
|
await page.screenshot({ path: screenshotPath("desktop-login.png"), fullPage: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("公网企业微信入口跳转到 OAuth 边界", async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
|
await page.goto("/login");
|
||||||
|
|
||||||
|
await expect(page.getByRole("button", { name: /企业微信/ })).toBeVisible();
|
||||||
|
await page.screenshot({ path: screenshotPath("mobile-login-before-wecom.png"), fullPage: true });
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: /企业微信/ }).click();
|
||||||
|
await expect(page).toHaveURL(/open\.weixin\.qq\.com|work\.weixin\.qq\.com|wechat_redirect/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("公网员工移动工作台与高频页面保持移动体验", async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
|
await loginLiveUser(page, "employee");
|
||||||
|
|
||||||
|
await page.goto("/mobile");
|
||||||
|
await expect(page.getByTestId("mobile-nav-home")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("mobile-nav-approvals")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("mobile-nav-capture")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("mobile-nav-me")).toBeVisible();
|
||||||
|
await expectNoDesignRegressionCopy(page);
|
||||||
|
await page.screenshot({ path: screenshotPath("mobile-home.png"), fullPage: true });
|
||||||
|
|
||||||
|
await page.goto("/mobile/reimbursements");
|
||||||
|
await expect(page.locator("table")).toHaveCount(0);
|
||||||
|
await expectNoDesignRegressionCopy(page);
|
||||||
|
await page.screenshot({ path: screenshotPath("mobile-reimbursements.png"), fullPage: true });
|
||||||
|
|
||||||
|
await page.goto("/mobile/leave");
|
||||||
|
await expect(page.locator("table")).toHaveCount(0);
|
||||||
|
await expectNoDesignRegressionCopy(page);
|
||||||
|
await page.screenshot({ path: screenshotPath("mobile-leave.png"), fullPage: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("公网 PC 报销与票据页面不回退到路径式标题", async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 1440, height: 960 });
|
||||||
|
const employee = await loginLiveUser(page, "employee");
|
||||||
|
|
||||||
|
await page.goto("/reimbursements");
|
||||||
|
await expect(page.getByRole("heading", { name: /报销/ })).toBeVisible();
|
||||||
|
await expectNoDesignRegressionCopy(page);
|
||||||
|
await page.screenshot({ path: screenshotPath("desktop-reimbursements.png"), fullPage: true });
|
||||||
|
|
||||||
|
await page.goto("/invoice-pool");
|
||||||
|
await expect(page.getByRole("heading", { name: "票据中心", exact: true })).toBeVisible();
|
||||||
|
await expectNoDesignRegressionCopy(page);
|
||||||
|
await page.screenshot({ path: screenshotPath("desktop-invoice-pool.png"), fullPage: true });
|
||||||
|
|
||||||
|
await logoutLiveUser(page, employee);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes
|
||||||
|
A1,R104 公网部署与验收合同,"把 R100-R103 的设计和报销主流程改动部署到公网试运行环境,并尽可能用自动化与视觉自动化完成验收。","本 CSV 校验通过;本批只做部署、验证、证据归档和必要脚本/文档补充,不改业务状态机和数据模型。","python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-07_10-38-35-r104-public-deploy-visual-acceptance.csv;业务流程影响:固定公网试运行验收范围。","python|manual",DONE,DONE,DONE,"issues/2026-05-07_10-38-35-r104-public-deploy-visual-acceptance.csv","[]","回滚路径:使用本次发布前备份和上一 trialrun tag 回滚;不提交密钥。"
|
||||||
|
A2,本地发布前门禁,"在本地执行部署契约、Prisma、前端类型检查/构建、设计规则和关键 Playwright,确认当前 HEAD 可发布。","本地 static gate、UI design 规则、R102/R103 Playwright 均通过;若 full-local 受环境限制,记录原因和替代覆盖。","node deploy/trialrun/validate-env-contract.js;cd server && npm run prisma:validate;cd client && npm run typecheck && npm run build;node scripts/qa/check-ui-design-rules.mjs;cd client && npx playwright test tests/e2e/r102-reimbursement-main-flow.spec.ts tests/e2e/r103-cross-page-ui-consistency.spec.ts --reporter=list;业务流程:报销主流程与跨页面 UI 基线可回归。","node|npm|playwright",DONE,DONE,DONE,"server|client|scripts/qa|deploy/trialrun","[""A1""]","Vite manual chunk 循环警告仍存在,已记录但不在本批修复。"
|
||||||
|
A3,发布 tag 与公网部署,"为当前提交创建 trialrun tag,执行 deploy/trialrun/deploy.sh 同步到 118.89.77.218 的 /opt/hyb-oa-trialrun,并完成远端 /ready、external deps smoke、runtime smoke。","存在 trialrun-20260507-01 tag;部署脚本成功;公网 /ready 成功;远端 runtime 与 external deps smoke 成功。","git tag -a trialrun-20260507-01 -m 'trialrun release 20260507-01';bash deploy/trialrun/deploy.sh;curl -fsS https://oa.hyinda.com/ready;业务流程:最新前后端发布到公网试运行环境。","git|ssh|rsync|docker|curl",DONE,DONE,DONE,"deploy/trialrun|remote:/opt/hyb-oa-trialrun","[""A2""]","部署脚本默认不同步 .env;远端已有敏感环境变量作为事实源。"
|
||||||
|
A4,公网 live smoke 与配置核验,"使用独立 smoke 账号执行公网只读 live smoke,并核验企业微信配置、移动默认 returnTo、OCR provider 当前策略。","smoke-live 通过或明确缺少凭据;/api/auth/wecom/config 返回 ready;wecom login 302 的 state/returnTo 指向 /mobile;记录 OCR_PROVIDER 是 mock 还是 tencent。","LIVE_EXPECT_WECOM_STATUS=ready bash scripts/qa/run-web-gates.sh smoke-live;curl -fsS https://oa.hyinda.com/api/auth/wecom/config;curl -I 'https://oa.hyinda.com/api/auth/wecom/login?returnTo=%2Fmobile';业务流程:公网登录、Portal、项目、公告、个人设置和企微入口只读可达。","bash|curl|playwright",DONE,DONE,DONE,"scripts/qa|remote:/opt/hyb-oa-trialrun/deploy/trialrun/.env","[""A3""]","smoke 账号凭据仅通过临时环境变量传入,未打印、未写入仓库;远端 OCR_PROVIDER=tencent。"
|
||||||
|
A5,公网视觉自动化核验,"新增或运行公网视觉验收脚本,覆盖桌面登录页、手机企业微信 UA 登录页、/mobile 落点边界、票据/报销关键页面,并保存截图。","公网视觉脚本通过;截图保存到 output/playwright/r104;页面不出现旧版口语化文案、黑色移动底栏、路由式标题或 UUID 主展示。","cd client && R104_PUBLIC_BASE_URL=https://oa.hyinda.com npx playwright test tests/e2e/r104-public-visual-acceptance.spec.ts --reporter=list;业务流程:公网桌面与移动视口关键页面视觉可用。","playwright",DONE,DONE,DONE,"client/tests/e2e/r104-public-visual-acceptance.spec.ts|output/playwright/r104","[""A3"",""A4""]","真实企业微信客户端已绑定直达 /mobile 仍需用户手机最终确认;自动化覆盖到 OAuth 跳转边界。"
|
||||||
|
A6,真实 OCR 辅助复核,"若公网启用 Tencent OCR,使用既有真实发票 PDF 执行 live OCR 自动化;若公网仍为 mock OCR,则记录策略并保留本地 Tencent OCR 已通过证据。","Tencent OCR 启用时 live OCR 通过;mock 策略时明确记录未做公网真实 OCR,后续单独开 OCR 切换 Issue。","cd client && PLAYWRIGHT_MANUAL=1 PLAYWRIGHT_OCR_LIVE=1 PLAYWRIGHT_OCR_LIVE_FILE=/home/shunli/Projects/hyb-oa/artifacts/北京悦海堂餐饮管理有限公司_发票金额1683.00元.pdf E2E_BASE_URL=https://oa.hyinda.com npx playwright test tests/e2e/manual/live-invoice-ocr.spec.ts --workers=1 --reporter=list;业务流程:票据上传→OCR→available。","playwright|tencent-ocr",DONE,DONE,DONE,"client/tests/e2e/manual/live-invoice-ocr.spec.ts|artifacts/r70/live-ocr-batch-report.md","[""A4""]","公网远端 OCR_PROVIDER=tencent,真实发票 PDF live OCR 通过;不把腾讯 secret 写入仓库。"
|
||||||
|
A7,证据归档与中文提交,"归档 R104 验证报告,回填 CSV 状态,提交 Issue 和必要测试脚本/报告变更。","存在 artifacts/r104-public-deploy-visual-acceptance/report.md;A1-A6 状态按实测回填;git diff --check 通过;中文提交引用 R104/A1-A7。","python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-07_10-38-35-r104-public-deploy-visual-acceptance.csv;git diff --check;git commit -m 'R104/A1-A7 公网部署与视觉验收';业务流程:发布证据可追溯。","python|git|manual",DONE,DONE,DONE,"issues/2026-05-07_10-38-35-r104-public-deploy-visual-acceptance.csv|artifacts/r104-public-deploy-visual-acceptance|client/tests/e2e/r104-public-visual-acceptance.spec.ts","[""A1"",""A2"",""A3"",""A4"",""A5"",""A6""]","报告不得包含密码、token、secret、cookie;提交前执行敏感词扫描和 staged diff check。"
|
||||||
|
在新工单中引用
屏蔽一个用户