R109/A6,A7 增加移动报销视觉专项回归
这个提交包含在:
@@ -12,6 +12,7 @@
|
|||||||
"test:e2e:full": "playwright test",
|
"test:e2e:full": "playwright test",
|
||||||
"test:e2e:r106-visual-audit": "playwright test tests/e2e/r106-visual-audit.spec.ts --workers=1 --reporter=list",
|
"test:e2e:r106-visual-audit": "playwright test tests/e2e/r106-visual-audit.spec.ts --workers=1 --reporter=list",
|
||||||
"test:e2e:r108-mobile-visual": "playwright test tests/e2e/r108-mobile-wecom-visual.spec.ts --workers=1 --reporter=list",
|
"test:e2e:r108-mobile-visual": "playwright test tests/e2e/r108-mobile-wecom-visual.spec.ts --workers=1 --reporter=list",
|
||||||
|
"test:e2e:r109-mobile-feedback": "playwright test tests/e2e/r109-mobile-reimbursement-feedback.spec.ts --workers=1 --reporter=list",
|
||||||
"test:e2e:r75-gap-fill": "playwright test tests/e2e/r75-automation-gap-fill.spec.ts --workers=1 --reporter=list",
|
"test:e2e:r75-gap-fill": "playwright test tests/e2e/r75-automation-gap-fill.spec.ts --workers=1 --reporter=list",
|
||||||
"test:e2e:r70-assist": "R70_TRIALRUN_ASSIST=1 playwright test tests/e2e/manual/r70-trialrun-assist.spec.ts --workers=1 --reporter=list",
|
"test:e2e:r70-assist": "R70_TRIALRUN_ASSIST=1 playwright test tests/e2e/manual/r70-trialrun-assist.spec.ts --workers=1 --reporter=list",
|
||||||
"test:e2e:r70-deep-assist": "PLAYWRIGHT_MANUAL=1 R70_TRIALRUN_DEEP_ASSIST=1 playwright test tests/e2e/manual/r70-trialrun-deep-assist.spec.ts --workers=1 --reporter=list",
|
"test:e2e:r70-deep-assist": "PLAYWRIGHT_MANUAL=1 R70_TRIALRUN_DEEP_ASSIST=1 playwright test tests/e2e/manual/r70-trialrun-deep-assist.spec.ts --workers=1 --reporter=list",
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { mkdirSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { expect, test, type Browser, type Page } from "@playwright/test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
EMPLOYEE,
|
||||||
|
createAuthenticatedApiClient,
|
||||||
|
type AuthenticatedApiClient,
|
||||||
|
type E2EUser,
|
||||||
|
} from "./helpers/auth";
|
||||||
|
|
||||||
|
const ROOT_DIR = path.resolve(process.cwd(), "..");
|
||||||
|
const SCREENSHOT_DIR = path.join(ROOT_DIR, "output/playwright/r109-mobile-feedback");
|
||||||
|
const WECOM_UA =
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 wxwork/4.1.20 MicroMessenger/8.0.1 Language/zh_CN";
|
||||||
|
|
||||||
|
type SeededReimbursement = {
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
client: AuthenticatedApiClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
function screenshotPath(fileName: string) {
|
||||||
|
mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
||||||
|
return path.join(SCREENSHOT_DIR, `${fileName}.png`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loginForMobile(page: Page, user: E2EUser) {
|
||||||
|
await page.goto("/login");
|
||||||
|
await expect(page.getByTestId("login-identifier")).toBeVisible();
|
||||||
|
await page.getByTestId("login-identifier").fill(user.identifier);
|
||||||
|
await page.getByTestId("login-password").fill(user.password);
|
||||||
|
await page.getByTestId("login-submit").click();
|
||||||
|
await page.waitForURL(/\/(portal|mobile)/, { timeout: 30_000 });
|
||||||
|
await page.waitForLoadState("networkidle").catch(() => undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newWecomPage(browser: Browser, viewport: { width: number; height: number }) {
|
||||||
|
const context = await browser.newContext({
|
||||||
|
viewport,
|
||||||
|
isMobile: true,
|
||||||
|
hasTouch: true,
|
||||||
|
userAgent: WECOM_UA,
|
||||||
|
locale: "zh-CN",
|
||||||
|
serviceWorkers: "block",
|
||||||
|
});
|
||||||
|
return { context, page: await context.newPage() };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function firstEnabledExpenseTypeId(client: AuthenticatedApiClient) {
|
||||||
|
const response = await client.api.get("/api/expense-types?enabled=true");
|
||||||
|
const payload = await response.json() as { data?: { items?: Array<{ id: string; enabled?: boolean }> } };
|
||||||
|
const item = payload.data?.items?.find((candidate) => candidate.enabled !== false) ?? payload.data?.items?.[0];
|
||||||
|
if (!response.ok() || !item?.id) {
|
||||||
|
throw new Error(`无法读取费用类型:HTTP ${response.status()}`);
|
||||||
|
}
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedDraftReimbursement(): Promise<SeededReimbursement> {
|
||||||
|
const client = await createAuthenticatedApiClient(EMPLOYEE);
|
||||||
|
const expenseTypeId = await firstEnabledExpenseTypeId(client);
|
||||||
|
const description = `R109 移动报销 ${Date.now()}`;
|
||||||
|
|
||||||
|
const reimbursementResponse = await client.api.post("/api/reimbursements", {
|
||||||
|
data: { description },
|
||||||
|
});
|
||||||
|
const reimbursementPayload = await reimbursementResponse.json() as { data?: { id?: string } };
|
||||||
|
if (!reimbursementResponse.ok() || !reimbursementPayload.data?.id) {
|
||||||
|
throw new Error(`创建报销草稿失败:HTTP ${reimbursementResponse.status()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemResponse = await client.api.post(`/api/reimbursements/${reimbursementPayload.data.id}/items`, {
|
||||||
|
data: {
|
||||||
|
expenseTypeId,
|
||||||
|
expenseDate: "2026-05-08",
|
||||||
|
amount: "168.30",
|
||||||
|
description: "R109 移动端默认绑定明细",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!itemResponse.ok()) {
|
||||||
|
throw new Error(`创建报销明细失败:HTTP ${itemResponse.status()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { id: reimbursementPayload.data.id, description, client };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectMobileMetrics(page: Page) {
|
||||||
|
return page.evaluate(() => {
|
||||||
|
function parseRgb(value: string) {
|
||||||
|
const match = value.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)/i);
|
||||||
|
if (!match) return null;
|
||||||
|
return {
|
||||||
|
r: Number(match[1]),
|
||||||
|
g: Number(match[2]),
|
||||||
|
b: Number(match[3]),
|
||||||
|
a: match[4] == null ? 1 : Number(match[4]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVisible(element: Element) {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
const style = getComputedStyle(element);
|
||||||
|
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
const nav = document.querySelector("[data-testid='mobile-nav-reimbursements']")?.closest("nav");
|
||||||
|
const navRect = nav?.getBoundingClientRect();
|
||||||
|
const activeNav = document.querySelector("nav a[aria-current='page']");
|
||||||
|
const activeStyle = activeNav ? getComputedStyle(activeNav) : null;
|
||||||
|
const activeRgb = activeStyle ? parseRgb(activeStyle.backgroundColor) : null;
|
||||||
|
const moreLinks = document.querySelector("[data-testid='mobile-more-links']");
|
||||||
|
const moreRect = moreLinks?.getBoundingClientRect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: window.innerWidth,
|
||||||
|
scrollWidth: Math.max(document.documentElement.scrollWidth, document.body?.scrollWidth ?? 0),
|
||||||
|
tableCount: Array.from(document.querySelectorAll("table")).filter(isVisible).length,
|
||||||
|
navHeight: navRect ? Math.round(navRect.height) : 0,
|
||||||
|
activeNavBackground: activeStyle?.backgroundColor ?? "",
|
||||||
|
activeNavAverage: activeRgb ? Math.round((activeRgb.r + activeRgb.g + activeRgb.b) / 3) : 255,
|
||||||
|
activeNavAlpha: activeRgb?.a ?? 0,
|
||||||
|
moreMenuRatio: moreRect ? moreRect.height / window.innerHeight : null,
|
||||||
|
activeElementTestId: document.activeElement?.getAttribute("data-testid") ?? "",
|
||||||
|
activeElementTag: document.activeElement?.tagName ?? "",
|
||||||
|
text: document.body?.innerText ?? "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe.serial("R109 企业微信移动报销反馈专项", () => {
|
||||||
|
test.skip(process.env.E2E_MODE === "live", "R109 本地移动专项只在临时本地环境运行");
|
||||||
|
|
||||||
|
test("移动报销详情、底部导航和添加发票交互可用", async ({ browser }) => {
|
||||||
|
const seeded = await seedDraftReimbursement();
|
||||||
|
const employee = await newWecomPage(browser, { width: 390, height: 844 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await loginForMobile(employee.page, EMPLOYEE);
|
||||||
|
|
||||||
|
await employee.page.goto("/mobile/reimbursements");
|
||||||
|
await employee.page.waitForLoadState("networkidle").catch(() => undefined);
|
||||||
|
const card = employee.page.getByTestId("mobile-reimbursement-card").filter({ hasText: seeded.description }).first();
|
||||||
|
await expect(card).toBeVisible();
|
||||||
|
await employee.page.screenshot({ path: screenshotPath("mobile-reimbursements") });
|
||||||
|
await card.getByRole("link", { name: "继续编辑" }).click();
|
||||||
|
await expect(employee.page).toHaveURL(new RegExp(`/mobile/reimbursements/${seeded.id}`));
|
||||||
|
await expect(employee.page.getByTestId("mobile-reimbursement-detail")).toBeVisible();
|
||||||
|
await employee.page.screenshot({ path: screenshotPath("mobile-reimbursement-detail") });
|
||||||
|
|
||||||
|
const detailMetrics = await collectMobileMetrics(employee.page);
|
||||||
|
expect(detailMetrics.scrollWidth).toBeLessThanOrEqual(detailMetrics.width + 2);
|
||||||
|
expect(detailMetrics.tableCount).toBe(0);
|
||||||
|
expect(detailMetrics.navHeight).toBeLessThanOrEqual(68);
|
||||||
|
expect(detailMetrics.text).not.toContain("/reimbursements/");
|
||||||
|
|
||||||
|
await employee.page.getByTestId("reimbursement-invoice-add").click();
|
||||||
|
await expect(employee.page.getByTestId("reimbursement-invoice-dialog")).toBeVisible();
|
||||||
|
const dialogOpenMetrics = await collectMobileMetrics(employee.page);
|
||||||
|
expect(dialogOpenMetrics.activeElementTestId).not.toBe("mobile-reimbursement-invoice-search");
|
||||||
|
|
||||||
|
await employee.page.getByTestId("reimbursement-invoice-upload-file").setInputFiles([
|
||||||
|
{
|
||||||
|
name: `r109-mobile-${Date.now()}.jpg`,
|
||||||
|
mimeType: "image/jpeg",
|
||||||
|
buffer: Buffer.from(`r109-mobile-upload-${Date.now()}`),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await employee.page.getByTestId("reimbursement-invoice-upload-submit").click();
|
||||||
|
await expect(employee.page.getByTestId("reimbursement-invoice-upload-submit")).toHaveText("上传发票", {
|
||||||
|
timeout: 20_000,
|
||||||
|
});
|
||||||
|
|
||||||
|
await employee.page.getByRole("button", { name: "已有发票" }).click();
|
||||||
|
await expect(employee.page.getByTestId("mobile-reimbursement-invoice-search")).toBeVisible();
|
||||||
|
await employee.page.screenshot({ path: screenshotPath("mobile-invoice-dialog") });
|
||||||
|
|
||||||
|
await employee.page.goto("/mobile/leave");
|
||||||
|
await expect(employee.page.getByTestId("mobile-leave-form")).toBeVisible();
|
||||||
|
await employee.page.screenshot({ path: screenshotPath("mobile-leave") });
|
||||||
|
const leaveMetrics = await collectMobileMetrics(employee.page);
|
||||||
|
expect(leaveMetrics.navHeight).toBeLessThanOrEqual(68);
|
||||||
|
expect(leaveMetrics.scrollWidth).toBeLessThanOrEqual(leaveMetrics.width + 2);
|
||||||
|
|
||||||
|
await employee.page.goto("/mobile/me");
|
||||||
|
await expect(employee.page.getByTestId("mobile-more-links")).toBeVisible();
|
||||||
|
await employee.page.screenshot({ path: screenshotPath("mobile-me") });
|
||||||
|
const meMetrics = await collectMobileMetrics(employee.page);
|
||||||
|
expect(meMetrics.moreMenuRatio ?? 0).toBeLessThanOrEqual(0.5);
|
||||||
|
expect(meMetrics.navHeight).toBeLessThanOrEqual(68);
|
||||||
|
expect(meMetrics.activeNavAlpha).toBeGreaterThan(0.9);
|
||||||
|
expect(meMetrics.activeNavAverage, meMetrics.activeNavBackground).toBeLessThan(245);
|
||||||
|
} finally {
|
||||||
|
await seeded.client.api.delete(`/api/reimbursements/${seeded.id}`).catch(() => undefined);
|
||||||
|
await Promise.all([seeded.client.dispose(), employee.context.close()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes
|
||||||
|
A1,移动底部导航压缩与实心选中态,"优化企业微信移动端底部导航:降低高度、减少空心胶囊感,选中态改为实心背景,并确保安全区和点击目标仍符合移动端可用性。","1) 390x844 视口底部导航高度控制在 64px 左右,含安全区不再显得过厚;2) 选中态为实心填充,不使用空心描边胶囊;3) 首页、报销、请假、票据、我的页面无底部遮挡;4) 保留至少 44px 点击目标。","命令:`bash scripts/qa/run-web-gates.sh r108-mobile-local`;业务流程:手机企微进入 /mobile,切换首页、票据、我的,确认底部导航紧凑且选中态清晰。","playwright|typescript",DONE,DONE,DONE,"client/src/components/mobile/MobileBottomNav.tsx|client/src/layouts/AppShellLayout.tsx|client/tests/e2e/r108-mobile-wecom-visual.spec.ts","[]","回滚路径:恢复 MobileBottomNav 样式和 AppShellLayout 移动 padding。"
|
||||||
|
A2,移动报销详情页,"新增或改造 `/mobile/reimbursements/:id`,让移动报销列表的继续编辑进入移动详情,而不是 PC 报销详情页。","1) `/mobile/reimbursements` 点击草稿继续编辑进入 `/mobile/reimbursements/:id`;2) 首屏显示金额、状态、明细数、票据数、提交/删除等高频操作;3) 不出现 PC 顶栏、侧边栏、打印按钮和冗长元信息;4) 提交操作不落在页面最底部不可见区域。","命令:`cd client && npm run typecheck && npm run build && bash ../scripts/qa/run-web-gates.sh r109-mobile-local`;业务流程:手机企微进入报销列表 -> 继续编辑草稿 -> 添加明细/查看票据 -> 提交或删除草稿。","playwright|typescript",DONE,DONE,DONE,"client/src/pages/MobileReimbursementsPage.tsx|client/src/pages/MobileReimbursementDetailPage.tsx|client/src/router/index.tsx|client/src/api/reimbursements.ts|client/tests/e2e","[""A1""]","不改后端状态机;如需要新增前端页面文件,沿用现有 API。"
|
||||||
|
A3,移动添加发票交互重做,"优化移动报销详情中的添加发票体验:打开后不自动聚焦搜索框,默认优先上传/拍照和展示可绑定发票,并默认绑定到明细。","1) 添加发票面板打开后 activeElement 不再是搜索输入框,企微不自动弹键盘;2) 移动端优先展示上传/拍照入口,其次展示可绑定发票;3) 若报销单只有 1 条明细,绑定发票默认选中该明细;4) 上传后给出 pending_ocr/available/failed 的明确状态,不只显示上传中。","命令:`bash scripts/qa/run-web-gates.sh r109-mobile-local`;业务流程:手机报销详情 -> 添加发票 -> 不弹键盘 -> 上传或选择发票 -> 默认绑定到明细。","playwright|typescript",DONE,DONE,DONE,"client/src/pages/ReimbursementDetailPage.tsx|client/src/pages/MobileReimbursementDetailPage.tsx|client/src/api/invoicePool.ts|client/tests/e2e","[""A2""]","优先前端交互改造;不在本 Issue 改 OCR provider。"
|
||||||
|
A4,移动票据上传卡住专项排障,"针对真机企微上传后长期显示上传中的问题做公网与本地双轨排障,区分前端按钮状态、HTTP 上传、存储写入、OCR pending、worker 处理和腾讯 OCR 响应。","1) 本地 mock 上传仍能在 2s 内恢复按钮状态;2) 公网 live 上传能记录 requestId、HTTP 状态、耗时和最终票据状态;3) 若卡住,报告能归因到前端 pending 未清、接口未返回、接口返回但 OCR pending、worker 未处理或腾讯失败;4) 不泄露凭据和发票隐私。","命令:`PLAYWRIGHT_OCR_LIVE_FILE=<file> PLAYWRIGHT_MANUAL=1 PLAYWRIGHT_OCR_LIVE=1 E2E_BASE_URL=https://oa.hyinda.com npx playwright test tests/e2e/manual/live-invoice-ocr.spec.ts --workers=1 --reporter=list`;业务流程:企微/公网员工上传真实发票 -> 记录上传按钮、接口响应、OCR 状态。","playwright|manual|postgres:query",DONE,DONE,TODO,"client/src/pages/MobileCapturePage.tsx|server/src/routes/invoicePool.js|server/src/jobs/ocrJob.js|server/src/scripts/audit-smoke-data.js|client/tests/e2e/manual/live-invoice-ocr.spec.ts","[""A1""]","已补 45s 上传超时保护并通过本地 R108/R109;公网真机 OCR 归因仍需要用户配合真实上传时间点或授权重置 smoke 账号。"
|
||||||
|
A5,移动请假与我的页面底部避让,"修复请假页创建草稿按钮贴近/被底部导航遮挡的问题,并精简我的页更多入口首屏占用。","1) `/mobile/leave` 创建草稿按钮不会被底部导航压住;2) 表单底部有足够滚动留白;3) `/mobile/me` 高频入口优先,低频入口折叠或下沉;4) 390x844 视觉巡检无底部遮挡发现。","命令:`bash scripts/qa/run-web-gates.sh r108-mobile-local`;业务流程:手机请假填写表单并滚动到底部,手机我的页面进入常用入口。","playwright|typescript",DONE,DONE,DONE,"client/src/pages/MobileLeavePage.tsx|client/src/pages/MobileMePage.tsx|client/tests/e2e/r108-mobile-wecom-visual.spec.ts","[""A1""]","只改移动端布局和入口排序,不改请假审批规则。"
|
||||||
|
A6,企微手机路由分流与视觉规则扩展,"扩展自动视觉巡检,把 PC 路由在企微手机视口下的误入问题纳入检测,重点覆盖报销详情、票据中心、请假 PC 路由。","1) 企微手机从移动入口不会跳入 PC 报销详情;2) 巡检能发现添加发票弹窗自动聚焦、底部导航过高、底部操作遮挡、PC 壳误入;3) 输出截图和 JSON/Markdown 报告。","命令:`bash scripts/qa/run-web-gates.sh r108-mobile-local && bash scripts/qa/run-web-gates.sh r109-mobile-local`;业务流程:企微手机首页、报销详情、添加发票、票据上传、请假、我的全链路巡检。","playwright|typescript",DONE,DONE,DONE,"client/tests/e2e/r108-mobile-wecom-visual.spec.ts|client/tests/e2e/r109-mobile-reimbursement-feedback.spec.ts|scripts/qa/run-web-gates.sh|output/playwright/r109-mobile-feedback","[""A2"",""A3"",""A5""]","截图输出到 output/playwright,不提交大图。"
|
||||||
|
A7,R109 回归收口,"执行 R109 全量相关验证,回填 Issue 状态并准备中文分批提交。","1) server Prisma validate 通过;2) client typecheck/build 通过;3) R108 移动视觉、R109 移动专项通过;4) 公网真机 OCR 归因单独等待人工配合,不和本轮 UI 修复混淆。","命令:`cd server && npm run prisma:validate && cd ../client && npm run typecheck && npm run build && cd .. && bash scripts/qa/run-web-gates.sh r108-mobile-local && bash scripts/qa/run-web-gates.sh r109-mobile-local`;业务流程:移动报销、发票、请假、我的、票据上传完成回归。","npm|playwright|git",DONE,DONE,DONE,"issues/2026-05-08_00-25-12-r109-mobile-reimbursement-trialrun-fixes.csv|client|server|scripts/qa|output/playwright/r109-mobile-feedback","[""A1"",""A2"",""A3"",""A4"",""A5"",""A6""]","提交信息使用中文并引用 R109/Ax;A4 公网真机 OCR 归因仍需人工配合。"
|
||||||
|
+26
-1
@@ -36,7 +36,7 @@ qa_require_cmd() {
|
|||||||
|
|
||||||
qa_usage() {
|
qa_usage() {
|
||||||
cat <<USAGE
|
cat <<USAGE
|
||||||
Usage: bash scripts/qa/run-web-gates.sh <smoke-local|full-local|r70-assist-local|r106-visual-local|r108-mobile-local|smoke-live>
|
Usage: bash scripts/qa/run-web-gates.sh <smoke-local|full-local|r70-assist-local|r106-visual-local|r108-mobile-local|r109-mobile-local|smoke-live>
|
||||||
|
|
||||||
可选环境变量:
|
可选环境变量:
|
||||||
QA_PORT / QA_HOST
|
QA_PORT / QA_HOST
|
||||||
@@ -471,6 +471,23 @@ qa_run_r108_mobile_local() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qa_run_r109_mobile_local() {
|
||||||
|
qa_select_port
|
||||||
|
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=1536}"
|
||||||
|
export JOBS_ENABLED="${JOBS_ENABLED:-true}"
|
||||||
|
qa_export_app_env
|
||||||
|
qa_prepare_db
|
||||||
|
qa_build_client
|
||||||
|
qa_start_server
|
||||||
|
|
||||||
|
echo ">>> 执行 R109 企业微信移动报销反馈专项"
|
||||||
|
(
|
||||||
|
cd "$CLIENT_DIR"
|
||||||
|
rm -rf test-results playwright-report
|
||||||
|
E2E_MODE=local npm run test:e2e:r109-mobile-feedback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
qa_run_live_smoke() {
|
qa_run_live_smoke() {
|
||||||
local live_base_url="${LIVE_SMOKE_BASE_URL:-https://oa.hyinda.com}"
|
local live_base_url="${LIVE_SMOKE_BASE_URL:-https://oa.hyinda.com}"
|
||||||
echo ">>> 探活 live /ready: ${live_base_url}/ready"
|
echo ">>> 探活 live /ready: ${live_base_url}/ready"
|
||||||
@@ -525,6 +542,14 @@ case "$MODE" in
|
|||||||
trap qa_cleanup EXIT
|
trap qa_cleanup EXIT
|
||||||
qa_run_r108_mobile_local
|
qa_run_r108_mobile_local
|
||||||
;;
|
;;
|
||||||
|
r109-mobile-local)
|
||||||
|
qa_require_cmd curl
|
||||||
|
qa_require_db_client
|
||||||
|
qa_require_cmd node
|
||||||
|
qa_require_cmd npm
|
||||||
|
trap qa_cleanup EXIT
|
||||||
|
qa_run_r109_mobile_local
|
||||||
|
;;
|
||||||
smoke-live)
|
smoke-live)
|
||||||
qa_require_cmd curl
|
qa_require_cmd curl
|
||||||
qa_require_cmd node
|
qa_require_cmd node
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户