diff --git a/artifacts/r104-public-deploy-visual-acceptance/report.md b/artifacts/r104-public-deploy-visual-acceptance/report.md new file mode 100644 index 00000000..0c4e777b --- /dev/null +++ b/artifacts/r104-public-deploy-visual-acceptance/report.md @@ -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 deploy@118.89.77.218 '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=smoke-admin@hyinda.com \ +LIVE_SMOKE_EMPLOYEE_IDENTIFIER=smoke-employee@hyinda.com \ +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=smoke-admin@hyinda.com \ +LIVE_SMOKE_EMPLOYEE_IDENTIFIER=smoke-employee@hyinda.com \ +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 账号下产生测试票据记录;后续如需要,可开清理或测试数据归档专项。 diff --git a/client/tests/e2e/r104-public-visual-acceptance.spec.ts b/client/tests/e2e/r104-public-visual-acceptance.spec.ts new file mode 100644 index 00000000..8d3cbfe5 --- /dev/null +++ b/client/tests/e2e/r104-public-visual-acceptance.spec.ts @@ -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); + }); +}); diff --git a/issues/2026-05-07_10-38-35-r104-public-deploy-visual-acceptance.csv b/issues/2026-05-07_10-38-35-r104-public-deploy-visual-acceptance.csv new file mode 100644 index 00000000..c835b90c --- /dev/null +++ b/issues/2026-05-07_10-38-35-r104-public-deploy-visual-acceptance.csv @@ -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。"