Files
DadaoTask/web/e2e/knowledge-paging.mjs
T
Steven d34a6ac69e
CI / backend (push) Canceled after 0s
feat(knowledge): 阅读改成独立整页,抽屉只用来编辑
知识原来一律在抽屉里打开,正文贴着屏幕右半边,长文读十几屏眼睛一直在右边。
阅读是「坐下来读一会儿」,编辑是「改两句就走」,两件事不该共用一个容器。

- 新增阅读页 /w/:wsId/knowledge/:itemId:正文列限到 40rem 居中,28px 标题,
  更新/发布/切片行,标签与来源,动作行放编辑与发布/归档/恢复
- 列表页与搜索结果改成跳阅读页,不再弹抽屉
- KnowledgeDrawer 加 initialEditing:进来就是编辑器,保存或取消直接关
- 文案常量搬到 lib/knowledge.ts,列表页与阅读页共用一份

顺带修三个 e2e 脚本:新建后进阅读页没有「关闭」按钮可点、侧栏色点按钮让树
顺序断言多出空串、翻页脚本还在用已关闭的短信登录(改密码登录)。
2026-11-03 12:12:51 +08:00

150 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 知识列表翻页的界面验收:点「加载更多」能把 84 条翻到底,不出错。
*
* 这条脚本是有来历的:接口的游标字段名写错(`cur.item_id`),第二页直接 500,
* 但库小于一页时永远碰不到——导入 84 条旧备忘之后用户一点就撞上了。
*
* 用法:
* node web/e2e/knowledge-paging.mjs # 默认打生产
* WEB=http://127.0.0.1:5180 PHONE=13900000001 node web/e2e/knowledge-paging.mjs
*/
import { readFile } from "node:fs/promises";
import { chromium } from "playwright-core";
const WEB = process.env.WEB ?? "http://192.168.8.188:8200";
const API = process.env.API ?? `${WEB}/api/v1`;
const PHONE = process.env.PHONE ?? "13605513683";
const CODE_PIN = process.env.CODE_PIN ?? "123456"; // 生产 pin 就是验证码本身
const CHROME =
process.env.CHROME ??
`${process.env.LOCALAPPDATA}\\ms-playwright\\chromium-1223\\chrome-win64\\chrome.exe`;
let pass = 0;
let fail = 0;
function check(name, ok, extra = "") {
if (ok) {
pass += 1;
console.log(`PASS ${name}`);
} else {
fail += 1;
console.log(`FAIL ${name} ${extra}`);
}
}
/**
* 登录:默认用密码(生产的短信登录已经关了),密码从 PASSWORD 拿,
* 或者从 .local/e2e-account-password.txt 读(dev 13900000001 的密码存在那儿)。
* 真没密码时才回落到短信验证码——那种环境下短信登录得开着。
*/
async function login() {
const password = await readPassword();
if (password) {
const r = await fetch(`${API}/auth/login/password`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone: PHONE, password }),
});
const body = await r.json();
if (body.code !== 0) throw new Error(`登录失败:${body.message}`);
return { tokens: body.data.tokens, workspaces: await workspaces(body.data.tokens) };
}
const sent = await fetch(`${API}/auth/sms/send`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone: PHONE }),
}).then((r) => r.json());
if (sent.code !== 0) throw new Error(`发码失败:${sent.message}(配个 PASSWORD 或写下密码文件就能走密码登录)`);
const login = await fetch(`${API}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone: PHONE, code: CODE_PIN }),
});
const body = await login.json();
if (body.code !== 0) throw new Error(`登录失败:${body.message}`);
return { tokens: body.data.tokens, workspaces: await workspaces(body.data.tokens) };
}
async function readPassword() {
if (process.env.PASSWORD) return process.env.PASSWORD;
try {
const file = new URL("../../.local/e2e-account-password.txt", import.meta.url);
const text = (await readFile(file, "utf8")).trim();
return text || null;
} catch {
return null;
}
}
async function workspaces(tokens) {
const r = await fetch(`${API}/workspaces`, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
const body = await r.json();
return body.data?.items ?? body.data ?? [];
}
async function main() {
const { tokens, workspaces: spaces } = await login();
if (!spaces.length) throw new Error("这个账号没有工作区");
const ws = spaces[0];
// 先问接口一共有多少条,作为界面上应该数出来的数字
const head = await fetch(`${API}/workspaces/${ws.id}/knowledge?limit=100`, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
const headBody = await head.json();
const total = (headBody.data?.items ?? []).length;
const browser = await chromium.launch({ executablePath: CHROME });
const context = await browser.newContext({ viewport: { width: 1440, height: 960 } });
await context.addInitScript(
([access, refresh]) => {
localStorage.setItem("dadao.access", access);
localStorage.setItem("dadao.refresh", refresh);
},
[tokens.access_token, tokens.refresh_token],
);
const page = await context.newPage();
const errors = [];
const bad = [];
page.on("pageerror", (e) => errors.push(String(e)));
page.on("response", (r) => {
if (r.url().includes("/knowledge?") && r.status() >= 400) bad.push(`${r.status()} ${r.url()}`);
});
await page.goto(`${WEB}/w/${ws.id}/knowledge`, { waitUntil: "networkidle" });
const cards = () => page.locator("li");
const first = await cards().count();
check("首屏有列表", first > 0, `count=${first}`);
let rounds = 0;
while (rounds < 20) {
const more = page.getByRole("button", { name: "加载更多" });
if ((await more.count()) === 0) break;
const before = await cards().count();
await more.first().click();
const grew = async () => (await cards().count()) > before;
for (let i = 0; i < 40 && !(await grew()); i += 1) await page.waitForTimeout(100);
if (!(await grew())) break;
rounds += 1;
}
const shown = await cards().count();
check("点「加载更多」能把列表翻到底", shown >= total, `shown=${shown} total=${total}`);
check("翻页接口没有报错", bad.length === 0, bad.slice(0, 3).join(" | "));
const toast = await page.locator("text=服务器内部错误").count();
check("页面上没有「服务器内部错误」", toast === 0);
check("控制台零报错", errors.length === 0, errors.slice(0, 2).join(" | "));
await page.screenshot({ path: "e2e/artifacts/knowledge-paging.png" });
await browser.close();
console.log(`\n${fail === 0 ? "ALL PASS" : "SOME FAILED"} — pass ${pass}, fail ${fail}`);
process.exit(fail === 0 ? 0 : 1);
}
main().catch((err) => {
console.error("脚本异常 —", err?.message ?? err);
process.exit(1);
});