含 AI 助手、抖音 OAuth、流量池推送、支付获客、SuperAdmin 设备与场景管理等本地迭代;以本地为准单向推送,未拉取远程。 Co-authored-by: Cursor <cursoragent@cursor.com>
268 lines
9.6 KiB
JavaScript
268 lines
9.6 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Batch3 · 存客宝前端 62 路由 E2E(Playwright headless)
|
||
* 用法: node Server/tests/batch3_frontend_e2e.mjs
|
||
*/
|
||
import { chromium } from "playwright";
|
||
|
||
const WEB = process.env.CKB_WEB || "http://localhost:3100";
|
||
const API = process.env.CKB_API || "http://localhost:8082";
|
||
const ACCOUNT = process.env.CKB_ACCOUNT || "karuo";
|
||
const PASSWORD = process.env.CKB_PASSWORD || "zhiqun1984";
|
||
|
||
async function apiLogin() {
|
||
const res = await fetch(`${API}/v1/auth/login`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ account: ACCOUNT, password: PASSWORD, typeId: 1 }),
|
||
});
|
||
const j = await res.json();
|
||
if (j.code !== 200) throw new Error("login failed: " + JSON.stringify(j));
|
||
const info = await fetch(`${API}/v1/auth/info`, {
|
||
headers: { Authorization: `Bearer ${j.data.token}` },
|
||
}).then((r) => r.json());
|
||
if (info.code !== 200) throw new Error("auth/info failed");
|
||
return { token: j.data.token, user: info.data, deviceTotal: info.data.deviceTotal ?? 1 };
|
||
}
|
||
|
||
async function fetchIds(token) {
|
||
const h = { Authorization: `Bearer ${token}` };
|
||
const get = (path) => fetch(`${API}${path}`, { headers: h }).then((r) => r.json());
|
||
const first = (j, key = "list") => {
|
||
const d = j.data;
|
||
const list = Array.isArray(d) ? d : (d?.list ?? []);
|
||
return list[0] ?? null;
|
||
};
|
||
const [dev, wx, wb, ct, plans] = await Promise.all([
|
||
get("/v1/devices?page=1&limit=1"),
|
||
get("/v1/wechats?page=1&limit=1"),
|
||
get("/v1/workbench/list?page=1&limit=1"),
|
||
get("/v1/content/library/list?page=1&limit=1"),
|
||
get("/v1/plan/list?page=1&limit=5&sceneId=8"),
|
||
]);
|
||
const device = first(dev);
|
||
const wechat = first(wx);
|
||
const workbench = first(wb);
|
||
const content = first(ct);
|
||
const payPlan = first(plans) || { id: 759 };
|
||
return {
|
||
deviceId: device?.id ?? 13287,
|
||
wechatId: wechat?.wechatId ?? wechat?.id ?? "wxid_test",
|
||
wechatRowId: wechat?.id ?? 1,
|
||
workbenchId: workbench?.id ?? 1,
|
||
contentId: content?.id ?? 88,
|
||
planId: payPlan?.id ?? 759,
|
||
scenarioId: 8,
|
||
scenarioName: encodeURIComponent("付款码获客"),
|
||
sceneName: encodeURIComponent("付款码获客"),
|
||
trafficPoolId: 1,
|
||
groupId: 1,
|
||
materialId: 1,
|
||
orderId: 1,
|
||
aiKey: "default",
|
||
reportToken: "qa-preview",
|
||
};
|
||
}
|
||
|
||
function buildRoutes(p) {
|
||
const r = (path, auth = true, note = "") => ({ path, auth, note });
|
||
return [
|
||
r("/", true),
|
||
r("/init", true),
|
||
r("/login", false),
|
||
r("/guide", false),
|
||
r("/pay/acquire?planId=" + p.planId, false, "公开付款页"),
|
||
r("/scenarios", true),
|
||
r("/scenarios/settings", true),
|
||
r("/scenarios/report/" + p.reportToken, false),
|
||
r("/scenarios/new", true),
|
||
r("/scenarios/new/8", true),
|
||
r("/scenarios/edit/" + p.planId, true),
|
||
r("/scenarios/list/8/付款码获客", true),
|
||
r("/scenarios/channel/8/付款码获客", true),
|
||
r("/scenarios/channel/8/付款码获客/new", true),
|
||
r("/scenarios/channel/8/付款码获客/edit/" + p.planId, true),
|
||
r("/oauth/douyin/callback", false),
|
||
r("/scenarios/ai/" + p.aiKey, true),
|
||
r("/workspace", true),
|
||
r("/workspace/auto-like", true),
|
||
r("/workspace/auto-like/new", true),
|
||
r("/workspace/auto-like/record/" + p.workbenchId, true),
|
||
r("/workspace/auto-like/edit/" + p.workbenchId, true),
|
||
r("/workspace/auto-group", true),
|
||
r("/workspace/auto-group/new", true),
|
||
r("/workspace/auto-group/" + p.workbenchId, true),
|
||
r("/workspace/auto-group/" + p.workbenchId + "/edit", true),
|
||
r("/workspace/group-create", true),
|
||
r("/workspace/group-create/new", true),
|
||
r("/workspace/group-create/" + p.workbenchId, true),
|
||
r("/workspace/group-create/" + p.workbenchId + "/groups", true),
|
||
r("/workspace/group-create/" + p.workbenchId + "/groups/" + p.groupId, true),
|
||
r("/workspace/group-create/" + p.workbenchId + "/edit", true),
|
||
r("/workspace/group-push", true),
|
||
r("/workspace/group-push/" + p.workbenchId, true),
|
||
r("/workspace/group-push/new", true),
|
||
r("/workspace/group-push/edit/" + p.workbenchId, true),
|
||
r("/workspace/moments-sync", true),
|
||
r("/workspace/moments-sync/new", true),
|
||
r("/workspace/moments-sync/record/" + p.workbenchId, true),
|
||
r("/workspace/moments-sync/edit/" + p.workbenchId, true),
|
||
r("/workspace/ai-assistant", true),
|
||
r("/workspace/ai-analyzer", true),
|
||
r("/workspace/ai-strategy", true, "占位页"),
|
||
r("/workspace/ai-forecast", true, "占位页"),
|
||
r("/workspace/traffic-distribution", true),
|
||
r("/workspace/traffic-distribution/new", true),
|
||
r("/workspace/traffic-distribution/edit/1", true),
|
||
r("/workspace/traffic-distribution/1", true),
|
||
r("/workspace/contact-import/list", true),
|
||
r("/workspace/contact-import/form", true),
|
||
r("/workspace/contact-import/form/1", true),
|
||
r("/workspace/contact-import/detail/1", true),
|
||
r("/workspace/ai-knowledge", true),
|
||
r("/workspace/ai-knowledge/new", true),
|
||
r("/workspace/ai-knowledge/1", true),
|
||
r("/workspace/ai-knowledge/1/edit", true),
|
||
r("/workspace/distribution-management", true),
|
||
r("/workspace/distribution-management/1", true),
|
||
r("/workspace/group-welcome", true),
|
||
r("/workspace/group-welcome/new", true),
|
||
r("/workspace/group-welcome/1", true),
|
||
r("/workspace/group-welcome/edit/1", true),
|
||
r("/mine", true),
|
||
r("/mine/devices", true),
|
||
r("/mine/devices/" + p.deviceId, true),
|
||
r("/mine/traffic-pool", true),
|
||
r("/mine/traffic-pool/list2", true),
|
||
r("/mine/traffic-pool/create", true),
|
||
r("/mine/traffic-pool/userList/" + p.trafficPoolId, true),
|
||
r("/mine/traffic-pool/detail/" + p.wechatId + "/" + p.trafficPoolId, true),
|
||
r("/wechat-accounts", true),
|
||
r("/wechat-accounts/detail/" + p.wechatRowId, true),
|
||
r("/recharge", true),
|
||
r("/recharge/order", true),
|
||
r("/recharge/order/" + p.orderId, true),
|
||
r("/recharge/buy-power", true),
|
||
r("/recharge/usage-records", true),
|
||
r("/settings", true),
|
||
r("/security", true),
|
||
r("/about", true),
|
||
r("/privacy", true),
|
||
r("/userSet", true),
|
||
r("/mine/content", true),
|
||
r("/mine/content/new", true),
|
||
r("/mine/content/edit/" + p.contentId, true),
|
||
r("/mine/content/materials/" + p.contentId, true),
|
||
r("/mine/content/materials/new/" + p.contentId, true),
|
||
r("/mine/content/materials/edit/" + p.contentId + "/1", true),
|
||
r("/test", true),
|
||
r("/test/select", true),
|
||
r("/test/upload", true),
|
||
r("/test/update-notification", true),
|
||
r("/test/iframe", true),
|
||
];
|
||
}
|
||
|
||
function persistUserStore(user, token) {
|
||
return {
|
||
value: {
|
||
state: {
|
||
user,
|
||
token,
|
||
token2: null,
|
||
isLoggedIn: true,
|
||
},
|
||
version: 1,
|
||
},
|
||
timestamp: Date.now(),
|
||
config: { compress: false, encrypt: false },
|
||
};
|
||
}
|
||
|
||
async function injectAuth(page, session) {
|
||
await page.goto(WEB + "/login", { waitUntil: "domcontentloaded" });
|
||
await page.evaluate(
|
||
({ store, tok }) => {
|
||
localStorage.setItem("token", tok);
|
||
localStorage.setItem("user-store", JSON.stringify(store));
|
||
},
|
||
{ store: persistUserStore(session.user, session.token), tok: session.token },
|
||
);
|
||
}
|
||
|
||
async function main() {
|
||
const session = await apiLogin();
|
||
const params = await fetchIds(session.token);
|
||
const routes = buildRoutes(params);
|
||
const browser = await chromium.launch({ headless: true });
|
||
const context = await browser.newContext({ viewport: { width: 390, height: 844 } });
|
||
const page = await context.newPage();
|
||
const consoleErrors = [];
|
||
page.on("pageerror", (e) => consoleErrors.push(String(e.message)));
|
||
page.on("console", (msg) => {
|
||
if (msg.type() === "error") consoleErrors.push(msg.text());
|
||
});
|
||
|
||
await injectAuth(page, session);
|
||
|
||
const results = [];
|
||
for (const route of routes) {
|
||
consoleErrors.length = 0;
|
||
const url = WEB + route.path;
|
||
try {
|
||
await page.goto(url, { waitUntil: "networkidle", timeout: 25000 });
|
||
await page.waitForTimeout(800);
|
||
const finalPath = new URL(page.url()).pathname;
|
||
const body = await page.content();
|
||
let status = "pass";
|
||
let reason = "";
|
||
|
||
if (route.auth && finalPath.startsWith("/login")) {
|
||
status = "fail";
|
||
reason = "被重定向到登录页";
|
||
} else if (/Unexpected Application Error|Cannot read properties of undefined/i.test(body)) {
|
||
status = "fail";
|
||
reason = "页面运行时错误";
|
||
} else if (consoleErrors.some((e) => /TypeError|ReferenceError|SyntaxError/i.test(e))) {
|
||
status = "fail";
|
||
reason = consoleErrors[0]?.slice(0, 80);
|
||
} else if (body.length < 200 && !route.note.includes("占位")) {
|
||
status = "warn";
|
||
reason = "内容过短";
|
||
}
|
||
|
||
results.push({ path: route.path, status, reason, note: route.note });
|
||
process.stdout.write(status === "pass" ? "." : status === "warn" ? "?" : "F");
|
||
} catch (e) {
|
||
results.push({ path: route.path, status: "fail", reason: String(e.message).slice(0, 100), note: route.note });
|
||
process.stdout.write("E");
|
||
}
|
||
}
|
||
console.log("\n");
|
||
|
||
await browser.close();
|
||
|
||
const passed = results.filter((x) => x.status === "pass").length;
|
||
const warned = results.filter((x) => x.status === "warn").length;
|
||
const failed = results.filter((x) => x.status === "fail").length;
|
||
const total = results.length;
|
||
const summary = {
|
||
batch: "Batch3",
|
||
total,
|
||
passed,
|
||
warned,
|
||
failed,
|
||
pass_rate: Math.round((passed / total) * 1000) / 10,
|
||
planId_scene8: params.planId,
|
||
failures: results.filter((x) => x.status === "fail"),
|
||
warnings: results.filter((x) => x.status === "warn"),
|
||
};
|
||
console.log(JSON.stringify(summary, null, 2));
|
||
process.exit(failed > 0 ? 1 : 0);
|
||
}
|
||
|
||
main().catch((e) => {
|
||
console.error(e);
|
||
process.exit(2);
|
||
});
|