Files
DadaoTask/web/e2e/integrations.mjs
Steven 1e8cd01b3d
CI / backend (push) Canceled after 0s
fix(integrations): 扫码确认后不再 500,回话不带链接,栏目改叫「机器人」
1. 扫码确认那一刻页面报「服务器内部错误」:`bot_channel` 的轮询口子直接
   `current_setting('app.bot_channel', true)::uuid`,而事务级 set_config 提交后
   会把连接上的变量还原成空串而不是 NULL,空串 cast 就 InvalidTextRepresentation。
   迁移 0015 改成 nullif(...,'')::uuid,与 app_ws_id() 同一写法。
2. 微信回话只回「已保存任务:…」/「已保存知识草稿:…」,不再带站点链接
   (PUBLIC_BASE_URL 是内网地址,手机上打不开)。
3. 栏目名「接入」改名「机器人」;页面标题与提示文案同步。

pytest 151 passed;ruff check/format 干净;tsc 0;vite build 通过;
web/e2e/integrations.mjs 7 项全绿。
2026-11-05 23:22:49 +08:00

117 lines
4.4 KiB
JavaScript

/**
* 微信接入的端到端验证:页面上点「扫码连接微信」→ 真拿一张二维码回来 → 画在页面上。
*
* 不扫这张码(那得有人拿手机扫),所以只验到"二维码出来了、通道在等扫码"为止;
* 扫码之后的落库与轮询由 `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);
});