/** * 微信接入的端到端验证:页面上点「扫码连接微信」→ 真拿一张二维码回来 → 画在页面上。 * * 不扫这张码(那得有人拿手机扫),所以只验到"二维码出来了、通道在等扫码"为止; * 扫码之后的落库与轮询由 `backend/tests/test_wechat_bot.py` 覆盖。 */ import { readFileSync } from "node:fs"; import { chromium } from "playwright-core"; // 上一轮 e2e 建的密码就在仓库根的 .local 里(web/e2e 往上两级) function devPassword() { if (process.env.PASSWORD) return process.env.PASSWORD; try { return readFileSync(new URL("../../.local/e2e-account-password.txt", import.meta.url), "utf8").trim(); } catch { return "dadao-e2e-2026"; } } const WEB = process.env.WEB || "http://localhost:5180"; const API = process.env.API || "http://127.0.0.1:8100/api/v1"; const PHONE = process.env.PHONE || "13900000001"; const PASSWORD = devPassword(); const CHROME = process.env.CHROME || `${process.env.LOCALAPPDATA}\\ms-playwright\\chromium-1223\\chrome-win64\\chrome.exe`; const results = []; function check(name, ok, detail = "") { results.push({ name, ok, detail }); console.log(`${ok ? "PASS" : "FAIL"} ${name}${detail ? ` — ${detail}` : ""}`); } async function login() { const resp = await fetch(`${API}/auth/login/password`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ phone: PHONE, password: PASSWORD }), }); const body = await resp.json(); if (body.code !== 0) throw new Error(`登录失败 ${JSON.stringify(body)}`); return body.data; } async function main() { const session = await login(); const token = session.tokens.access_token; const headers = { authorization: `Bearer ${token}`, "content-type": "application/json" }; const wsList = await (await fetch(`${API}/workspaces`, { headers })).json(); const ws = wsList.data.items[0]; if (!ws) throw new Error("这个账号没有工作区"); const browser = await chromium.launch({ executablePath: CHROME, headless: true }); const context = await browser.newContext({ viewport: { width: 1440, height: 960 } }); const page = await context.newPage(); const consoleErrors = []; page.on("console", (m) => m.type() === "error" && consoleErrors.push(m.text())); page.on("pageerror", (e) => consoleErrors.push(String(e))); await page.addInitScript( ([access, refresh]) => { localStorage.setItem("dadao.access", access); localStorage.setItem("dadao.refresh", refresh); }, [session.tokens.access_token, session.tokens.refresh_token], ); await page.goto(`${WEB}/w/${ws.id}/integrations`, { waitUntil: "networkidle" }); check("机器人页打开", await page.getByText("怎么用").isVisible()); const button = page.getByRole("button", { name: "扫码连接微信" }); if (await button.isVisible()) { await button.click(); } else { // 之前连过:卡片上给的是「重新扫码」 await page.getByRole("button", { name: "重新扫码" }).click(); } const card = page.getByTestId("wechat-qr"); await card.waitFor({ state: "visible", timeout: 20_000 }); const img = card.locator("img"); const src = await img.getAttribute("src"); check( "二维码是页面里画出来的 SVG", Boolean(src?.startsWith("data:image/svg+xml;base64,")), src?.slice(0, 32), ); check( "提示用微信扫码", (await card.innerText()).includes("用微信扫这张码"), (await card.innerText()).split("\n")[0], ); const channels = await (await fetch(`${API}/workspaces/${ws.id}/integrations`, { headers })).json(); const channel = channels.data.items[0]; check("后端记下了这条通道", Boolean(channel), JSON.stringify(channel?.status)); check("还没扫码,状态是 pending", channel?.status === "pending", channel?.status); check("没有登录态就不该在轮询", channel?.polling === false, String(channel?.polling)); await page.screenshot({ path: "e2e/artifacts/integrations.png", fullPage: false }); check("控制台没有报错", consoleErrors.length === 0, consoleErrors.slice(0, 2).join(" | ")); await browser.close(); const failed = results.filter((r) => !r.ok); console.log( `\n${failed.length === 0 ? "ALL PASS" : "FAILED"} — pass ${results.length - failed.length}, fail ${failed.length}`, ); if (failed.length > 0) process.exit(1); } main().catch((err) => { console.error("FAIL 脚本异常 —", err.message); process.exit(1); });