CI / backend (push) Canceled after 0s
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 项全绿。
117 lines
4.4 KiB
JavaScript
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);
|
|
});
|