/** * 截图脚本:把导入的备忘录点开,看「浏览」样式是否真的渲染成 Markdown。 * * 只用于出图,不做断言;账号用 dev 的 13900000001(导入数据的归属)。 */ import { chromium } from "playwright-core"; import { mkdirSync } from "node:fs"; const WEB = process.env.WEB ?? "http://127.0.0.1:5180"; const API = process.env.API ?? "http://127.0.0.1:8100/api/v1"; const PHONE = process.env.PHONE ?? "13900000001"; const WS_NAME = process.env.WS_NAME ?? "我的备忘"; async function call(method, path, body, token) { const res = await fetch(`${API}${path}`, { method, headers: { "content-type": "application/json", ...(token ? { authorization: `Bearer ${token}` } : {}), ...(method === "GET" ? {} : { "idempotency-key": `shot-${Math.random()}` }), }, body: body ? JSON.stringify(body) : undefined, }); const text = await res.text(); const json = text ? JSON.parse(text) : {}; if (json.code !== 0) throw new Error(`${method} ${path} -> ${json.code} ${json.message}`); return json.data; } async function loginAs(phone) { // dev 下验证码固定,发码接口有 60 秒节流:能直接登录就直接登,被节流就等够了再发 try { return await call("POST", "/auth/login", { phone, code: "123456" }); } catch { /* 验证码过期或没发过,往下走发码 */ } for (let i = 0; i < 2; i += 1) { try { await call("POST", "/auth/sms/send", { phone }); break; } catch (err) { if (!String(err.message).includes("2011") || i === 1) throw err; await new Promise((r) => setTimeout(r, 61_000)); } } return await call("POST", "/auth/login", { phone, code: "123456" }); } mkdirSync("e2e/artifacts", { recursive: true }); const login = await loginAs(PHONE); const token = login.tokens.access_token; const me = await call("GET", "/auth/me", undefined, token); const ws = me.workspaces.find((w) => w.name === WS_NAME) ?? me.workspaces[0]; if (!ws) throw new Error(`${PHONE} 名下没有工作区`); console.log(`工作区:${ws.name} ${ws.id}`); const browser = await chromium.launch({ executablePath: process.env.CHROME ?? `${process.env.LOCALAPPDATA}\\ms-playwright\\chromium-1223\\chrome-win64\\chrome.exe`, }); const context = await browser.newContext({ viewport: { width: 1440, height: 960 } }); const page = await context.newPage(); await page.addInitScript( ([access, refresh]) => { localStorage.setItem("dadao.access", access); localStorage.setItem("dadao.refresh", refresh); }, [token, login.tokens.refresh_token], ); await page.goto(`${WEB}/w/${ws.id}/knowledge`, { waitUntil: "networkidle" }); await page.getByRole("heading", { name: "知识" }).waitFor({ timeout: 20_000 }); // 导入的备忘都是发布过的,切到「已发布」才看得到;ALL=1 时留在默认的「全部」 if (!process.env.ALL) { await page.getByRole("button", { name: "已发布", exact: true }).first().click(); } if (process.env.TITLE) { // 挑一条带标题/列表的,截图里才看得出 Markdown 真的渲染了 const target = page.locator("li").filter({ hasText: process.env.TITLE }).first(); await target.waitFor({ timeout: 20_000 }); await page.screenshot({ path: "e2e/artifacts/memo-list.png" }); await target.getByRole("button", { name: "打开", exact: true }).click(); } else { await page.getByRole("button", { name: "打开", exact: true }).first().waitFor({ timeout: 20_000 }); await page.waitForTimeout(600); await page.screenshot({ path: "e2e/artifacts/memo-list.png" }); await page.getByRole("button", { name: "打开", exact: true }).first().click(); } await page.locator(".prose-dadao").first().waitFor({ timeout: 20_000 }); await page.waitForTimeout(600); await page.screenshot({ path: "e2e/artifacts/memo-read.png" }); if (process.env.EDIT) { // 编辑态:正文编辑器要占满剩下的高度,不能只露几行 await page.getByRole("button", { name: "编辑", exact: true }).last().click(); await page.locator(".vditor-ir [contenteditable]").first().waitFor({ timeout: 20_000 }); await page.waitForTimeout(800); await page.screenshot({ path: "e2e/artifacts/memo-edit.png" }); } await browser.close(); console.log("OK");