Harden auth/vault path sanitization and clean WeKnora docs

This commit is contained in:
Agent Board
2026-07-28 17:04:27 +08:00
parent 2deaf59f7b
commit 26ff1a9c9a
190 changed files with 13454 additions and 4987 deletions
+25 -16
View File
@@ -19,7 +19,7 @@ echo $!
sleep 8 && grep -E 'gateway 已启动|Running' /tmp/mnote-dev.log
```
**关键点:**
- 启动命令是 `npm run dev:hot`不是 desktop:hot
- 启动命令是 `npm run dev:hot`唯一热启动入口
- `dev:hot` 默认启用 `MNOTE_WEB_ALLOW_DEV_FIXTURES=1`
- 使用 cargo-watch 自动重编译
- 端口固定为 3000
@@ -35,8 +35,9 @@ paseo_browser_wait url=localhost:3000 timeoutMs=15000
# 3. 截图(如果返回 "tab not painted",等 3 秒重试)
paseo_browser_screenshot
# 4. 快速登录(snapshot 找到 @e5 即"测试账号快速登录"按钮)
paseo_browser_click ref=@e5
# 4. 标准登录(7-76:无「测试账号快速登录按钮)
# 在 snapshot 中定位账号/密码输入框与「登录」提交按钮后填表提交;
# 或脚本侧:require('./lib/browser-auth-login').loginViaAuthForm(page)
# 5. 等待进入工作区
paseo_browser_wait url=localhost:3000 text=工作区
@@ -45,10 +46,18 @@ paseo_browser_wait url=localhost:3000 text=工作区
paseo_browser_screenshot
```
### 4. 测试账号
- 邮箱:`mnote.e2e@example.com`
- 密码:`MnoteE2E123!`
- 用户名:`mnote-e2e`
### 4. 测试账号7-76 方案 Aadmin / user / AI 主体分离)
| 用途 | 用户名 | 邮箱 | 密码(本地/dev | control-plane `role` | admin 能力 |
|------|--------|------|------------------|----------------------|------------|
| **Ops admin** | `mnote-admin` | `mnote.admin@example.com` | `MnoteAdmin123!` | `admin` | 是(`/admin/*`、access-policy、代签) |
| **AI 主体** | `mnote-e2e` | `mnote.e2e@example.com` | `MnoteE2E123!` | `ai_service` | **否** |
| **个人用户示例** | `liaibo` | (本地) | (本地) | `user` | **否** |
- 登录方式:标准表单 / `POST /api/auth` / `scripts/lib/browser-auth-login.js`
- 产品/AI 主体:`loginViaAuthForm` / `loginViaAuthApi`(默认 e2e
- 管理面 seed`loginAsAdminViaAuthForm` / `withAdminBrowserSession`
- **不要**依赖快速登录按钮;**不要**把 `mnote-e2e` 放进 `MNOTE_ADMIN_USER_IDS``access-policy.admins`
### 5. 常见问题
| 问题 | 原因 | 解决 |
@@ -56,7 +65,6 @@ paseo_browser_screenshot
| ERR_CONNECTION_REFUSED | 服务器未启动 | 先运行 `npm run dev:hot` |
| tab not painted | 页面未渲染完成 | 等待 3 秒后重试截图 |
| Paseo 终端无输出 | 前台进程阻塞 | 用 `nohup ... &` 后台启动 |
| `desktop:hot` vs `dev:hot` | 启动命令不同 | 浏览器测试用 `dev:hot` |
### 6. AI 密码本读密 / 登录态(12-2 · **不依赖 3000**
@@ -97,7 +105,7 @@ cargo test -p mnote-web --lib routes::vault -- --nocapture 2>&1 | rg -n "login_r
- 2026-05-20 起,默认主入口固定按 `http://127.0.0.1:3000` 理解;历史记录里的 `3001` 只代表当时临时 mnote-web 实例,不再作为默认验收入口。
- 历史 smoke 分为 `current``retired/debug` 两类维护;退役脚本不应进入默认回归,除非脚本自身要求显式环境变量。
- 2026-05-27 起,默认 smoke 基线只覆盖 `3000 Rust SSR + leptos-tiptap + local-first` 主路径;Convex export、Convex 兼容、Next、3104、BlockNote 默认路径脚本不再放在默认候选里。
- 依赖 `/api/dev/seed` 的 browser smoke 默认使用 `npm run dev:hot``dev:hot` 默认启用 `MNOTE_WEB_ALLOW_DEV_FIXTURES=1``desktop:hot`生产启动仍默认关闭。修改 Node 启动脚本或环境变量后必须重启 `dev:hot` 主进程,不能只等待 cargo-watch 重载 Rust。
- 依赖 `/api/dev/seed` 的 browser smoke 默认使用 `npm run dev:hot``dev:hot` 默认启用 `MNOTE_WEB_ALLOW_DEV_FIXTURES=1`,生产启动仍默认关闭。修改 Node 启动脚本或环境变量后必须重启 `dev:hot` 主进程,不能只等待 cargo-watch 重载 Rust。
## 0. 当前 smoke 状态清单
@@ -124,7 +132,7 @@ node scripts/task490-runtime-surfaces-smoke.js
- Rust SSR 文档页 / Page Aggregatelocal-first 默认优先 `task167-local-markdown-title-body-options-no-convex-smoke.js`Page Aggregate browser conversion / compat fallback 改动补跑 `task522-page-aggregate-compat-fallback-contract.js`cloud/control-plane 文档可补跑 `task110-page-title-single-truth-smoke.js``task-page-aggregate-body-sync-smoke.js``task-page-aggregate-options-sync-smoke.js``task-page-aggregate-refresh-persistence-smoke.js`
- leptos-tiptap runtime surface`task490-runtime-surfaces-smoke.js`;需要验证保存回读可补跑 `task121-rust-web-editor-island-hydration-smoke.js`,但它仍使用 `/api/tree/commands create` 准备文档,不作为无 Convex 默认基线;需要验证浮层互斥和更多菜单状态时再跑 `task158-e30-menu-state-smoke.js`
- local-first 本地工作区:`task164-desktop-hot-local-folder-main-entry-smoke.js``task166-local-first-managed-workspace-no-convex-smoke.js``task167-local-markdown-title-body-options-no-convex-smoke.js``task436-local-markdown-open-document-external-change-smoke.js``task443-local-markdown-asset-upload-smoke.js``task451-local-markdown-conflict-resolution-ui-smoke.js``task452-local-search-index-browser-smoke.js``task453-local-folder-page-ai-changed-files-smoke.js`WorkspacePath / ObjectIdentity runtime 消费统一改动补跑 `task524-workspace-object-identity-matrix-smoke.js``task452` 只覆盖普通本地搜索、settings API、tag/backlink API 和旧 local-index UI 不复活;资料库问答、PDF/Office/image source ingestion 与引用回跳改跑 LightRAG / provider-neutral knowledge-rag 脚本。
- LightRAG / Knowledge RAG 资料库:LightRAG 是默认 provider。资料源、摄取、source scope 与 watcher 改动优先跑 `task532-knowledge-rag-docx-ingestion-smoke.js``task533-knowledge-rag-source-watcher-sync-smoke.js``task534-knowledge-rag-source-management-scope-smoke.js``task538-knowledge-rag-source-scope-api-smoke.js`;引用和 Page AI final answer 改动补跑 `task529-knowledge-rag-citation-resource-tab-smoke.js``task530-knowledge-rag-page-ai-final-answer-smoke.js`;真实 Pi + LightRAG 服务可用时补跑 `task-pi-lab-real-lightrag-smoke.js`WeKnora 仅是历史/备用 provider,不进入默认回归。旧 OpenHub 专项资料库 smoke 已软归档到 `recycle/20260712-pi-ts-openhub-retirement/`;旧 `task526-local-folder-ocr-api-smoke.js` / OCR sidecar 链路已退役并软归档;`/api/search/documents` 不作为 LightRAG、LiteParse、OCR sidecar 或 evidence.sqlite 的资料库问答 fallback。
- LightRAG / Knowledge RAG 资料库:LightRAG 是默认 provider。资料源、摄取、source scope 与 watcher 改动优先跑 `task532-knowledge-rag-docx-ingestion-smoke.js``task533-knowledge-rag-source-watcher-sync-smoke.js``task534-knowledge-rag-source-management-scope-smoke.js``task538-knowledge-rag-source-scope-api-smoke.js`;引用和 Page AI final answer 改动补跑 `task529-knowledge-rag-citation-resource-tab-smoke.js``task530-knowledge-rag-page-ai-final-answer-smoke.js`;真实 Pi + LightRAG 服务可用时补跑 `task-pi-lab-real-lightrag-smoke.js`。旧 OpenHub 专项资料库 smoke 已软归档到 `recycle/20260712-pi-ts-openhub-retirement/`;旧 `task526-local-folder-ocr-api-smoke.js` / OCR sidecar 链路已退役并软归档;`/api/search/documents` 不作为 LightRAG、LiteParse、OCR sidecar 或 evidence.sqlite 的资料库问答 fallback。
- local Markdown conflict regression`task510-local-markdown-conflict-regression-group.js` 串联真实冲突、连续上传、新建页面、外部恢复、多 tab CAS 与上传 409 孤儿策略;可用 `MNOTE_CONFLICT_REGRESSION_TASKS=a.js,b.js` 做定点子集。
- tree realtime / live cache`task446-tree-rename-dual-browser-live-smoke.js``task447-tree-move-order-dual-browser-live-smoke.js``task448-tree-resync-recovery-dual-browser-smoke.js``task449-tree-sse-reconnect-snapshot-recovery-smoke.js`
- 资源对象与 mindmap`task455-local-folder-mindmap-clean-smoke.js``task456-resource-object-shell-sync-smoke.js``task166-mindmap-phase6-block-smoke.js``task167-mindmap-kmind-parity-smoke.js``task168-mindmap-put-validator-smoke.js`Page AI mindmap skill / 资源生成改动补跑 `task503-mindmap-skill-capability-smoke.js`,真实 mindmap resource tab / Page AI target 改动补跑 `task525-page-ai-mindmap-resource-target-smoke.js`
@@ -139,7 +147,7 @@ node scripts/task490-runtime-surfaces-smoke.js
- 旧 Convex 兼容 smoke 已软删除到 `recycle/20260527-legacy-cleanup/convex/scripts/`,包括 `task163``task174``task175``task427``task428``task429``task430``task431``task432``task433``task434`
- `task179-tree-create-delete-no-reload-smoke.js`:覆盖 `/tree` debug shell。`/tree` 已是显式 debug/internal 边界,脚本默认阻断;只有设置 `MNOTE_ALLOW_DEBUG_TREE_SMOKE=1` 时才可执行。
- `task123-rust-web-tree-live-stream-consumer-smoke.js`:只证明 `/api/tree/events` SSE fallback 仍可用。当前 realtime 主链是 WebSocket push + SSE fallback;验证主链时优先使用 446 / 447 / 448,只有排查 fallback 时再跑 123。
- WeKnora 默认 provider 脚本(`task544``task769``task772``task777``task781``task783``task784``task785``task787``task788``task789``task790``task79x`)已退役;它们与 LightRAG 默认 provider 主线相反
- 历史非 LightRAG 默认 provider 脚本(`task544``task769``task772``task777``task781``task783``task784``task785``task787``task788``task789``task790``task79x`)已退役并软归档到 `recycle/scripts/` 下对应归档目录(及历史 recycle);不进入 LightRAG 默认回归
- Hermes/ACP/Reasonix 页面 AI 主流程 smoke 已退役;只保留 `task-hermes-page-ai-retirement-guard.js` 防止旧 runtime 复活。`task762-page-ai-board-first-smoke.js` 也已退役。
-`task019``task021``task022` 这类早期 UI regression 脚本已软删除到 `recycle/scripts/retired-ui-regressions/`,只作为历史对照;后续默认不要用于当前 Rust SSR / local-first 主路径验收。
- `task494-filetree-lazy-loading-dedup-smoke.js``task498-starred-page-tree-scope-and-local-edit-smoke.js``task499-sidebar-tree-view-state-smoke.js`2026-07-21 诊断为 fixture/契约过时(非 Playwright 安装问题),已迁到 `recycle/scripts/obsolete-tree-smokes-20260721/`。全局 Playwright 栈:`~/.agent-infra/playwright-stack`(见该目录 `use-in-project.md`)。树移动排序优先 `task447-tree-move-order-dual-browser-live-smoke.js` 或 Hermes tree QA。
@@ -236,8 +244,9 @@ node scripts/task490-runtime-surfaces-smoke.js
- 默认测试账号:
- 邮箱:`mnote.e2e@example.com`
- 密码:`MnoteE2E123!`
- 快捷按钮:`测试账号快速登录`
- 通过 API 或 UI 兜底完成认证
- UI:标准登录表单(`#account` + `#password` + `[data-auth-submit]`
- 共享 helper`scripts/lib/browser-auth-login.js``tree-shell-smoke-helpers.js`
- 通过 API 或 UI 兜底完成认证(7-76 P0 起无快速登录按钮)
- 为 smoke 自动创建、重命名、清理临时页面
后续新脚本优先复用这些 helper,不要在每个任务里再复制一套登录和清理逻辑。
@@ -246,7 +255,7 @@ node scripts/task490-runtime-surfaces-smoke.js
- 需要 `setupWorkspaceAccess``seedAiPolicy``seedAiRuntime` 等测试数据准备能力时,先确认服务由 `npm run dev:hot` 启动。
- `npm run dev:hot` 默认开启 dev fixtures;可用 `MNOTE_WEB_ALLOW_DEV_FIXTURES=0 npm run dev:hot` 显式关闭。
- `npm run desktop:hot` 保持生产近似的安全默认,不自动开放 `/api/dev/seed`;确需复用时显式执行 `MNOTE_WEB_ALLOW_DEV_FIXTURES=1 npm run desktop:hot`
- 生产启动不自动开放 `/api/dev/seed`;本地仅 `dev:hot` 默认开启 fixtures
- smoke 遇到 `dev_seed_disabled` 时不得标记为“功能未验证后跳过”;应先按上述契约重启服务,再重新执行完整 smoke。
## 2. 当前推荐测试顺序
@@ -411,7 +420,7 @@ node scripts/task097-homepage-entry-smoke.js
推荐:
- `开启宽版后 htmlWide 应为 true,实际 false`
- `测试账号登录后仍停留在 /auth`
- `标准登录后仍停留在 /auth`
- `本地首屏未捕获工作区特征`
不推荐:
@@ -524,7 +533,7 @@ const controlPlaneEnv = buildControlPlaneTestEnv(dataRoot, process.env);
### 7.3 例外
- `scripts/lib/control-plane-dev-seed.js``scripts/lib/control-plane-test-env.js` 本身是批准 helper。
- `scripts/desktop-hot.js``scripts/dev-hot.js``scripts/prod-build-start.js` 作为启动基础设施,设置环境变量路径是必要的。
- `scripts/mnote-web-hot.js``scripts/dev-hot.js``scripts/prod-build-start.js` 作为启动基础设施,设置环境变量路径是必要的。
- `rust/crates/` 下的 Rust 代码通过 `ControlPlaneStore` trait 访问数据库是正常路径。
- `scripts/task-control-plane-admin-libsql-roundtrip-smoke.js` 使用 `cargo run --bin control-plane-admin`,走 Rust admin CLI。
- `sqlite` fallback、admin CLI、legacy evidence/local_search 不在脚本纪律约束范围内。
+6 -7
View File
@@ -4,12 +4,11 @@
/**
* dev:hot 启动入口。
*
* 目标:
* - 不改动 desktop:hot 的默认行为。
* - 使用 cargo-watch 自动重编译并重启 mnote-web。
* - 通过 MNOTE_WEB_DEV_HOT_RELOAD 启用页面端轻量 reload 轮询。
* - 默认启用 MNOTE_WEB_ALLOW_DEV_FIXTURES,保证依赖 /api/dev/seed 的 smoke 可直接运行;
* 可显式设为 0 关闭desktop:hot / prod:start 保持默认关闭。
* 可显式设为 0 关闭prod:start 保持关闭。
* - 实际进程编排在 scripts/mnote-web-hot.js。
*/
const { spawn } = require("node:child_process");
@@ -17,7 +16,7 @@ const fs = require("node:fs");
const path = require("node:path");
const rootDir = path.resolve(__dirname, "..");
const desktopHotPath = path.join(rootDir, "scripts", "desktop-hot.js");
const hotPath = path.join(rootDir, "scripts", "mnote-web-hot.js");
function cargoWatchCommand(env = process.env) {
const extraArgs = String(env.MNOTE_WEB_DEV_HOT_CARGO_WATCH_ARGS || "").trim();
@@ -91,11 +90,11 @@ function buildDevHotEnv(baseEnv = process.env) {
}
function main() {
if (!fs.existsSync(desktopHotPath)) {
throw new Error(`缺少 desktop-hot 入口:${desktopHotPath}`);
if (!fs.existsSync(hotPath)) {
throw new Error(`缺少 mnote-web-hot 入口:${hotPath}`);
}
const child = spawn(process.execPath, [desktopHotPath], {
const child = spawn(process.execPath, [hotPath], {
cwd: rootDir,
env: buildDevHotEnv(process.env),
stdio: "inherit",
+168
View File
@@ -0,0 +1,168 @@
"use strict";
/**
* 7-76 P0:浏览器 smoke 标准登录(无「测试账号快速登录」按钮)。
* 优先填表提交 /auth;失败时可回退 API signIn/signUp(由调用方决定)。
*/
const DEFAULT_EMAIL = process.env.MNOTE_E2E_EMAIL || "mnote.e2e@example.com";
const DEFAULT_PASSWORD = process.env.MNOTE_E2E_PASSWORD || "MnoteE2E123!";
const DEFAULT_USERNAME = process.env.MNOTE_E2E_USERNAME || "mnote-e2e";
/** 方案 A:独立 ops admin(非 AI 主体、非个人笔记主账号) */
const DEFAULT_ADMIN_EMAIL = process.env.MNOTE_ADMIN_EMAIL || "mnote.admin@example.com";
const DEFAULT_ADMIN_PASSWORD = process.env.MNOTE_ADMIN_PASSWORD || "MnoteAdmin123!";
const DEFAULT_ADMIN_USERNAME = process.env.MNOTE_ADMIN_USERNAME || "mnote-admin";
const DEFAULT_UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
function resolveBaseUrl(baseUrl) {
return String(baseUrl || process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(
/\/+$/,
"",
);
}
/**
* 在已打开的 /auth 页(或任意页)用标准表单登录。
* @param {import('playwright').Page} page
* @param {{ baseUrl?: string, email?: string, password?: string, username?: string, timeoutMs?: number, gotoAuth?: boolean }} [options]
*/
async function loginViaAuthForm(page, options = {}) {
const baseUrl = resolveBaseUrl(options.baseUrl);
const email = options.email || DEFAULT_EMAIL;
const password = options.password || DEFAULT_PASSWORD;
const timeoutMs = options.timeoutMs || DEFAULT_UI_TIMEOUT_MS;
const gotoAuth = options.gotoAuth !== false;
if (gotoAuth || !String(page.url() || "").includes("/auth")) {
await page.goto(`${baseUrl}/auth`, { waitUntil: "commit", timeout: timeoutMs });
}
const account = page.locator("#account, input[name='account']").first();
const passwordInput = page.locator("#password, input[name='password']").first();
const submit = page.locator("[data-auth-submit], button[type='submit']").first();
await account.waitFor({ state: "visible", timeout: timeoutMs });
await account.fill(email, { timeout: timeoutMs });
await passwordInput.fill(password, { timeout: timeoutMs });
await submit.click({ timeout: timeoutMs });
}
/**
* API 层 signIn;不存在时可 signUp 再 signIn。
* @param {{ fetch: Function }} requestContext Playwright request 或兼容对象
* @param {{ baseUrl?: string, email?: string, password?: string, username?: string, timeoutMs?: number }} [options]
*/
async function loginViaAuthApi(requestContext, options = {}) {
const baseUrl = resolveBaseUrl(options.baseUrl);
const email = options.email || DEFAULT_EMAIL;
const password = options.password || DEFAULT_PASSWORD;
const username = options.username || DEFAULT_USERNAME;
const timeoutMs = options.timeoutMs || DEFAULT_UI_TIMEOUT_MS;
async function post(params) {
const response = await requestContext.fetch(`${baseUrl}/api/auth`, {
method: "POST",
headers: { "content-type": "application/json" },
data: {
action: "auth:signIn",
args: {
provider: "password",
params,
},
},
timeout: timeoutMs,
});
const text = await response.text();
let payload = null;
try {
payload = text ? JSON.parse(text) : null;
} catch {
payload = text;
}
return { ok: response.ok(), status: response.status(), payload };
}
let result = await post({
account: email,
email,
password,
flow: "signIn",
});
if (result.ok) {
return result.payload;
}
result = await post({
email,
name: username,
password,
flow: "signUp",
});
if (result.ok) {
return result.payload;
}
const details =
typeof result.payload === "string" ? result.payload : JSON.stringify(result.payload);
throw new Error(`标准 API 登录失败: ${result.status} ${details}`);
}
/**
* 以 ops admin 登录(/admin/*、代签、access-policy、ai-admin settings)。
* AI 主体 mnote-e2e 与个人账号 liaibo 默认无 admin 能力。
*/
async function loginAsAdminViaAuthForm(page, options = {}) {
return loginViaAuthForm(page, {
...options,
email: options.email || DEFAULT_ADMIN_EMAIL,
password: options.password || DEFAULT_ADMIN_PASSWORD,
username: options.username || DEFAULT_ADMIN_USERNAME,
});
}
async function loginAsAdminViaAuthApi(requestContext, options = {}) {
return loginViaAuthApi(requestContext, {
...options,
email: options.email || DEFAULT_ADMIN_EMAIL,
password: options.password || DEFAULT_ADMIN_PASSWORD,
username: options.username || DEFAULT_ADMIN_USERNAME,
});
}
/**
* 在独立 browser context 里以 admin 会话执行 fn(page),不污染主测用户 cookie。
* 用于:access-policy grants、/api/ai-admin/* 等必须 admin 的 seed。
* @param {import('playwright').Browser} browser
* @param {(page: import('playwright').Page) => Promise<T>} fn
* @param {{ baseUrl?: string, timeoutMs?: number }} [options]
* @returns {Promise<T>}
*/
async function withAdminBrowserSession(browser, fn, options = {}) {
const baseUrl = resolveBaseUrl(options.baseUrl);
const timeoutMs = options.timeoutMs || DEFAULT_UI_TIMEOUT_MS;
const context = await browser.newContext();
const page = await context.newPage();
try {
await loginAsAdminViaAuthForm(page, { baseUrl, timeoutMs });
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout: timeoutMs })
.catch(() => {});
return await fn(page);
} finally {
await context.close().catch(() => {});
}
}
module.exports = {
DEFAULT_EMAIL,
DEFAULT_PASSWORD,
DEFAULT_USERNAME,
DEFAULT_ADMIN_EMAIL,
DEFAULT_ADMIN_PASSWORD,
DEFAULT_ADMIN_USERNAME,
loginViaAuthForm,
loginViaAuthApi,
loginAsAdminViaAuthForm,
loginAsAdminViaAuthApi,
withAdminBrowserSession,
};
+1 -1
View File
@@ -24,7 +24,7 @@ async function postDevSeed(requestContext, baseUrl, seeds, timeoutMs) {
[
`/api/dev/seed 未启用(base=${normalizedBaseUrl})。`,
"依赖 seed 的 smoke 必须使用 `npm run dev:hot` 启动;dev:hot 默认开启 MNOTE_WEB_ALLOW_DEV_FIXTURES=1。",
"若复用 desktop:hot,请使用 `MNOTE_WEB_ALLOW_DEV_FIXTURES=1 npm run desktop:hot`。",
"若直接跑 mnote-web 而未走 dev:hot,请显式 `MNOTE_WEB_ALLOW_DEV_FIXTURES=1`。",
"修改 scripts/dev-hot.js 或启动环境后必须重启 dev:hot 主进程,cargo-watch 不会刷新 Node 启动环境。",
].join(" "),
);
@@ -92,7 +92,7 @@ function resolveRuntimePlan(env = process.env) {
const publicPort = Number.isFinite(frontendPort) ? Math.floor(frontendPort) : 3000;
const controlPlaneBackend = String(env.MNOTE_CONTROL_PLANE_BACKEND || "libsql-local").trim() || "libsql-local";
if (controlPlaneBackend === "sqlite") {
throw new Error("desktop:hot 不再支持 SQLite control-plane fallback;请使用 libsql-local/turso-local-replica/turso-remote/turso-synced");
throw new Error("dev:hot 不再支持 SQLite control-plane fallback;请使用 libsql-local/turso-local-replica/turso-remote/turso-synced");
}
const controlPlaneEnv = {
MNOTE_CONTROL_PLANE_BACKEND: controlPlaneBackend,
@@ -11,7 +11,7 @@ const {
resolveRuntimePlan,
shouldStartBackend,
stopStaleMnoteWebCargoProcesses,
} = require("./desktop-hot.js");
} = require("./mnote-web-hot.js");
function findFreePort() {
return new Promise((resolve, reject) => {
@@ -173,10 +173,10 @@ test("热启动计划拒绝 SQLite runtime fallback", () => {
test("显式配置知识库 provider 时透传给 MNote host", () => {
const plan = resolveRuntimePlan({
FRONTEND_PORT: "3000",
MNOTE_KNOWLEDGE_PROVIDER: "weknora",
MNOTE_KNOWLEDGE_PROVIDER: "lightrag_legacy",
});
assert.equal(plan.mnoteWebEnv.MNOTE_KNOWLEDGE_PROVIDER, "weknora");
assert.equal(plan.mnoteWebEnv.MNOTE_KNOWLEDGE_PROVIDER, "lightrag_legacy");
});
test("默认跳过 FastAPI 后端,只有显式开启时才启动", () => {
+6 -7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
/**
* Minimal Browser QA: Block handle menu icon/layout check
* Scope: open editor → hover paragraph handle → click handle → check menu
@@ -61,15 +62,13 @@ async function run() {
try {
// ── Step 1: Login ──
let s1 = step("login", "Login via quick-login button");
let s1 = step("login", "Login via standard auth form (7-76 P0)");
console.log(`\n🔍 Step 1: ${s1.desc}`);
await page.goto(`${BASE}/auth`, { waitUntil: "networkidle", timeout: 15000 });
const loginBtn = page.getByRole("button", { name: "测试账号快速登录" });
await loginBtn.waitFor({ state: "visible", timeout: 8000 });
await Promise.all([
page.waitForURL((url) => !url.pathname.includes("/auth"), { timeout: 15000 }),
loginBtn.click(),
]);
await loginViaAuthForm(page, { baseUrl: BASE, timeoutMs: 15000, gotoAuth: false });
await page
.waitForURL((url) => !url.pathname.includes("/auth"), { timeout: 15000 })
.catch(() => {});
await page.waitForTimeout(2000);
const afterLogin = page.url();
s1.status = afterLogin.includes("/auth") ? "FAIL" : "PASS";
+10 -12
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const path = require("path");
const { chromium } = require("playwright");
@@ -68,18 +69,15 @@ async function ensureAuthenticated(page, requestContext) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: TIMEOUT_MS });
await page.waitForTimeout(1000);
const btn = page.getByRole("button", { name: "测试账号快速登录" });
if (await btn.isVisible().catch(() => false)) {
await btn.click();
await page.waitForURL("**/documents/**", { timeout: TIMEOUT_MS }).catch(() => {});
await page.waitForTimeout(2000);
} else {
await page.getByLabel("邮箱").fill(TEST_EMAIL);
await page.getByLabel("密码").fill(TEST_PASSWORD);
await page.getByRole("button", { name: /登录|Login|Sign in/i }).click();
await page.waitForURL("**/documents/**", { timeout: TIMEOUT_MS }).catch(() => {});
await page.waitForTimeout(2000);
}
await loginViaAuthForm(page, {
baseUrl: BASE_URL,
email: TEST_EMAIL,
password: TEST_PASSWORD,
timeoutMs: TIMEOUT_MS,
gotoAuth: false,
});
await page.waitForURL((url) => !String(url).includes("/auth"), { timeout: TIMEOUT_MS }).catch(() => {});
await page.waitForTimeout(2000);
return null;
}
+25 -7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const path = require("node:path");
@@ -18,13 +19,30 @@ const E2E_USER_NAME = process.env.MNOTE_AI_ADMIN_E2E_USERNAME || "ai-user";
const E2E_USER_PASSWORD = process.env.MNOTE_AI_ADMIN_E2E_PASSWORD || ["Mnote", "E2E", "123!"].join("");
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit" });
const button = page.getByRole("button", { name: "测试账号快速登录" });
await button.waitFor({ state: "visible" });
await Promise.all([
page.waitForURL((url) => !url.pathname.includes("/auth")),
button.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function ensurePasswordUser(browser, user) {
+2 -2
View File
@@ -29,9 +29,9 @@ assert.equal(env.MNOTE_TURSO_LOCAL_PATH, "/mnt/Data1T/Mnote_data/control-plane/c
assert.equal(env.MNOTE_CONTROL_PLANE_DB_PATH, undefined);
const explicitKnowledgeProviderEnv = buildDevHotEnv({
MNOTE_KNOWLEDGE_PROVIDER: "weknora",
MNOTE_KNOWLEDGE_PROVIDER: "lightrag_legacy",
});
assert.equal(explicitKnowledgeProviderEnv.MNOTE_KNOWLEDGE_PROVIDER, "weknora");
assert.equal(explicitKnowledgeProviderEnv.MNOTE_KNOWLEDGE_PROVIDER, "lightrag_legacy");
const loopbackEnv = buildDevHotEnv({
MNOTE_WEB_BIND: "127.0.0.1:3300",
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
// Pi Lab API endpoint smoke
// 验证 Pi Lab API 路由在 mnote-web 开发环境下的响应
// 需要 mnote-web 已在运行(npm run desktop:hot 或独立启动)
// 需要 mnote-web 已在运行(npm run dev:hot 或独立启动)
// 检查:新端点 start/send/abort/events、SSE、permission-system managed builtin tools、receipt、no polling
const BASE = process.env.MNOTE_PI_LAB_BASE || 'http://127.0.0.1:3000';
+11 -6
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env node
const { loginViaAuthForm } = require('./lib/browser-auth-login');
// Pi Lab browser smoke
// 验证 Pi Lab 默认独立悬浮入口 + MNote-native drawer,不复用已退役 host drawer/provider tab/iframe。
// 需要 mnote-web 已在运行;MNOTE_PAGE_AI_PI_LAB 默认开启,设为 0 时才强制关闭。
@@ -38,12 +39,16 @@ async function addAuth(context) {
async function quickLoginIfNeeded(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (!(await quickLoginButton.isVisible({ timeout: 4000 }).catch(() => false))) return;
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: UI_TIMEOUT_MS }).catch(() => null),
quickLoginButton.click(),
]);
const accountReady = await page
.locator("#account")
.first()
.isVisible({ timeout: 4000 })
.catch(() => false);
if (!accountReady) return;
await loginViaAuthForm(page, { baseUrl: BASE, timeoutMs: UI_TIMEOUT_MS, gotoAuth: false });
await page
.waitForURL((url) => !url.toString().includes("/auth"), { timeout: UI_TIMEOUT_MS })
.catch(() => null);
}
async function approveVisiblePiDialog(page) {
+25 -7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
@@ -27,13 +28,30 @@ const CHROMIUM_EXECUTABLE = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE
|| (fs.existsSync("/usr/bin/google-chrome") ? "/usr/bin/google-chrome" : "");
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm, withAdminBrowserSession } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -29,13 +30,30 @@ function mkdirp(dir) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
@@ -89,42 +107,45 @@ function policyForFullAccessAskUser() {
};
}
async function seedWorkspace(page) {
async function seedWorkspace(browser, page) {
mkdirp(ROOT_PATH);
fs.writeFileSync(path.join(ROOT_PATH, PAGE_PATH), "# Pi full access question smoke\n", "utf8");
const grantResponse = await page.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const grantText = await grantResponse.text();
let grantBody = {};
try {
grantBody = grantText ? JSON.parse(grantText) : {};
} catch {
grantBody = { raw: grantText };
}
if (!grantResponse.ok() && grantBody.code !== "local_access_policy_grant_duplicate") {
throw new Error(`POST /api/admin/access-policy/grants failed: ${grantResponse.status()} ${grantText.slice(0, 800)}`);
}
await requestJson(page, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policyForFullAccessAskUser(),
quota: { daily: 200 },
},
});
// 方案 Aadmin 能力仅 mnote-admin
await withAdminBrowserSession(browser, async (adminPage) => {
const grantResponse = await adminPage.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const grantText = await grantResponse.text();
let grantBody = {};
try {
grantBody = grantText ? JSON.parse(grantText) : {};
} catch {
grantBody = { raw: grantText };
}
if (!grantResponse.ok() && grantBody.code !== "local_access_policy_grant_duplicate") {
throw new Error(`POST /api/admin/access-policy/grants failed: ${grantResponse.status()} ${grantText.slice(0, 800)}`);
}
await requestJson(adminPage, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policyForFullAccessAskUser(),
quota: { daily: 200 },
},
});
}, { baseUrl: BASE, timeoutMs: TIMEOUT });
await requestJson(page, `/api/ai-settings/effective?workspaceId=${encodeURIComponent(WORKSPACE_ID)}`);
}
@@ -259,7 +280,7 @@ async function main() {
try {
await quickLogin(page);
await seedWorkspace(page);
await seedWorkspace(browser, page);
await abortExistingSession(page);
const session = await startRealPi(page);
result.session = {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -35,13 +36,30 @@ function mkdirp(dir) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
+25 -7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -29,13 +30,30 @@ const CHROMIUM_EXECUTABLE = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE
|| (fs.existsSync("/usr/bin/google-chrome") ? "/usr/bin/google-chrome" : "");
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function main() {
+25 -7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -34,13 +35,30 @@ function mkdirp(dir) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
+25 -7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -29,13 +30,30 @@ const CHROMIUM_EXECUTABLE = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE
|| (fs.existsSync("/usr/bin/google-chrome") ? "/usr/bin/google-chrome" : "");
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const button = page.getByRole("button", { name: "测试账号快速登录" });
await button.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
button.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function fetchJson(page, url, options = {}) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -35,13 +36,30 @@ function mkdirp(dir) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (!(await quickLoginButton.isVisible({ timeout: 4000 }).catch(() => false))) return;
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
+63 -42
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm, withAdminBrowserSession } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -31,13 +32,30 @@ function mkdirp(dir) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
@@ -134,43 +152,46 @@ function policyForPlanMode() {
};
}
async function seedWorkspace(page) {
async function seedWorkspace(browser, page) {
mkdirp(ROOT_PATH);
fs.writeFileSync(path.join(ROOT_PATH, PAGE_PATH), "# Pi plan mode browser smoke\n", "utf8");
fs.writeFileSync(TARGET_ABS, TARGET_ORIGINAL, "utf8");
const grantResponse = await page.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const grantText = await grantResponse.text();
let grantBody = {};
try {
grantBody = grantText ? JSON.parse(grantText) : {};
} catch {
grantBody = { raw: grantText };
}
if (!grantResponse.ok() && grantBody.code !== "local_access_policy_grant_duplicate") {
throw new Error(`POST /api/admin/access-policy/grants failed: ${grantResponse.status()} ${grantText.slice(0, 800)}`);
}
await requestJson(page, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policyForPlanMode(),
quota: { daily: 200 },
},
});
// 方案 Aadmin 能力仅 mnote-admin
await withAdminBrowserSession(browser, async (adminPage) => {
const grantResponse = await adminPage.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const grantText = await grantResponse.text();
let grantBody = {};
try {
grantBody = grantText ? JSON.parse(grantText) : {};
} catch {
grantBody = { raw: grantText };
}
if (!grantResponse.ok() && grantBody.code !== "local_access_policy_grant_duplicate") {
throw new Error(`POST /api/admin/access-policy/grants failed: ${grantResponse.status()} ${grantText.slice(0, 800)}`);
}
await requestJson(adminPage, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policyForPlanMode(),
quota: { daily: 200 },
},
});
}, { baseUrl: BASE, timeoutMs: TIMEOUT });
await requestJson(page, `/api/ai-settings/effective?workspaceId=${encodeURIComponent(WORKSPACE_ID)}`);
}
@@ -279,7 +300,7 @@ async function main() {
try {
await quickLogin(page);
await seedWorkspace(page);
await seedWorkspace(browser, page);
await abortExistingSession(page);
const session = await startPlanSession(page);
result.session = {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm, withAdminBrowserSession } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -108,13 +109,30 @@ function mkdirp(dir) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
@@ -259,48 +277,51 @@ function policyForMatrix() {
};
}
async function seedWorkspace(page) {
async function seedWorkspace(browser, page) {
mkdirp(ROOT_PATH);
fs.writeFileSync(
path.join(ROOT_PATH, "pi-extension-mcp-matrix.md"),
"# Pi extension and MCP matrix smoke\n",
"utf8",
);
const grantResponse = await page.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const grantText = await grantResponse.text();
let grantBody = {};
try {
grantBody = grantText ? JSON.parse(grantText) : {};
} catch {
grantBody = { raw: grantText };
}
if (!grantResponse.ok() && grantBody.code !== "local_access_policy_grant_duplicate") {
throw new Error(
`POST /api/admin/access-policy/grants failed: ${grantResponse.status()} ${grantText.slice(0, 1000)}`,
);
}
await requestJson(page, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policyForMatrix(),
quota: { daily: 200 },
},
});
// 方案 Aadmin 能力仅 mnote-admin
await withAdminBrowserSession(browser, async (adminPage) => {
const grantResponse = await adminPage.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const grantText = await grantResponse.text();
let grantBody = {};
try {
grantBody = grantText ? JSON.parse(grantText) : {};
} catch {
grantBody = { raw: grantText };
}
if (!grantResponse.ok() && grantBody.code !== "local_access_policy_grant_duplicate") {
throw new Error(
`POST /api/admin/access-policy/grants failed: ${grantResponse.status()} ${grantText.slice(0, 1000)}`,
);
}
await requestJson(adminPage, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policyForMatrix(),
quota: { daily: 200 },
},
});
}, { baseUrl: BASE, timeoutMs: TIMEOUT });
return requestJson(page, `/api/ai-settings/effective?workspaceId=${encodeURIComponent(WORKSPACE_ID)}`);
}
@@ -576,7 +597,7 @@ async function main() {
try {
await quickLogin(page);
result.seededEffective = await seedWorkspace(page);
result.seededEffective = await seedWorkspace(browser, page);
result.checks.policy = {
mcpServers: ["CodeGraph", "SearXNG", "Chrome Bridge"].every(
(name) => (result.seededEffective.mcpServers || []).some(
+67 -44
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm, withAdminBrowserSession } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -28,13 +29,30 @@ function mkdirp(dir) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
@@ -78,45 +96,50 @@ function policyForLightRag() {
};
}
async function ensureDirectoryGrant(page) {
const response = await page.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const text = await response.text();
let body = {};
try {
body = text ? JSON.parse(text) : {};
} catch {
body = { raw: text };
}
if (response.ok()) return body;
if (body && body.code === "local_access_policy_grant_duplicate") return body;
throw new Error(`POST /api/admin/access-policy/grants failed: ${response.status()} ${text.slice(0, 800)}`);
async function ensureDirectoryGrant(browser) {
// 方案 Aadmin 能力仅 mnote-admine2e 为 AI 主体,不可调 /api/admin/*
return withAdminBrowserSession(browser, async (adminPage) => {
const response = await adminPage.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const text = await response.text();
let body = {};
try {
body = text ? JSON.parse(text) : {};
} catch {
body = { raw: text };
}
if (response.ok()) return body;
if (body && body.code === "local_access_policy_grant_duplicate") return body;
throw new Error(`POST /api/admin/access-policy/grants failed: ${response.status()} ${text.slice(0, 800)}`);
}, { baseUrl: BASE, timeoutMs: TIMEOUT });
}
async function seedRuntimePolicy(page) {
async function seedRuntimePolicy(browser) {
mkdirp(ROOT_PATH);
await ensureDirectoryGrant(page);
await requestJson(page, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policyForLightRag(),
quota: { daily: 200 },
},
});
await ensureDirectoryGrant(browser);
await withAdminBrowserSession(browser, async (adminPage) => {
await requestJson(adminPage, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policyForLightRag(),
quota: { daily: 200 },
},
});
}, { baseUrl: BASE, timeoutMs: TIMEOUT });
}
async function ensureLightRagReady(page) {
@@ -271,7 +294,7 @@ async function main() {
try {
await quickLogin(page);
await seedRuntimePolicy(page);
await seedRuntimePolicy(browser);
result.lightRagStatus = await ensureLightRagReady(page);
result.seededEffective = await requestJson(page, `/api/ai-settings/effective?workspaceId=${encodeURIComponent(WORKSPACE_ID)}`);
result.checks.lightRagToolsEnabled = [
+67 -44
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm, withAdminBrowserSession } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -33,13 +34,30 @@ function mkdirp(dir) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
@@ -133,46 +151,51 @@ function policyForAllSkillsAndMcp() {
};
}
async function ensureDirectoryGrant(page) {
const response = await page.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const text = await response.text();
let body = {};
try {
body = text ? JSON.parse(text) : {};
} catch {
body = { raw: text };
}
if (response.ok()) return body;
if (body && body.code === "local_access_policy_grant_duplicate") return body;
throw new Error(`POST /api/admin/access-policy/grants failed: ${response.status()} ${text.slice(0, 800)}`);
async function ensureDirectoryGrant(browser) {
// 方案 Aadmin 能力仅 mnote-admine2e 为 AI 主体,不可调 /api/admin/*
return withAdminBrowserSession(browser, async (adminPage) => {
const response = await adminPage.request.fetch(`${BASE}/api/admin/access-policy/grants`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
},
data: {
userId: ACTOR_ID,
rootUri: ROOT_URI,
rootPath: ROOT_PATH,
permission: "write",
recursive: true,
capabilities: ["ai"],
},
timeout: TIMEOUT,
});
const text = await response.text();
let body = {};
try {
body = text ? JSON.parse(text) : {};
} catch {
body = { raw: text };
}
if (response.ok()) return body;
if (body && body.code === "local_access_policy_grant_duplicate") return body;
throw new Error(`POST /api/admin/access-policy/grants failed: ${response.status()} ${text.slice(0, 800)}`);
}, { baseUrl: BASE, timeoutMs: TIMEOUT });
}
async function seedRuntimePolicy(page) {
async function seedRuntimePolicy(browser) {
mkdirp(ROOT_PATH);
await ensureDirectoryGrant(page);
await ensureDirectoryGrant(browser);
const policy = policyForAllSkillsAndMcp();
await requestJson(page, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policy,
quota: { daily: 200 },
},
});
await withAdminBrowserSession(browser, async (adminPage) => {
await requestJson(adminPage, "/api/ai-admin/settings", {
method: "PUT",
data: {
...policy,
quota: { daily: 200 },
},
});
}, { baseUrl: BASE, timeoutMs: TIMEOUT });
}
async function abortExistingSession(page) {
@@ -373,7 +396,7 @@ async function main() {
try {
await quickLogin(page);
await seedRuntimePolicy(page);
await seedRuntimePolicy(browser);
result.seededEffective = await requestJson(page, "/api/ai-settings/effective");
result.checks.allSkillsEnabled = ["VPN", "Chrome Bridge", "Context7", "SearXNG Search", "Global Search", "MemPalace", "CodeGraph"]
.every((name) => (result.seededEffective.skills || []).some((skill) => skill.name === name && skill.enabled !== false));
+11 -6
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env node
const { loginViaAuthForm } = require('./lib/browser-auth-login');
// Pi Lab RPC browser smoke
// 验证真实 Pi RPC + Omniroute/gpt-5.4-mini 在 MNote-native Pi Lab 抽屉中的可见 stream。
// 需要 mnote-web 已以 MNOTE_PAGE_AI_PI_LAB_RUNTIME=rpc 启动,并配置 MNOTE_PAGE_AI_PI_BIN / Omniroute key。
@@ -67,12 +68,16 @@ async function addAuth(context) {
async function quickLoginIfNeeded(page) {
const authResponse = await page.goto(`${BASE}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
if (!authResponse || authResponse.status() >= 400) return;
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (!(await quickLoginButton.isVisible({ timeout: 4000 }).catch(() => false))) return;
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: UI_TIMEOUT_MS }).catch(() => null),
quickLoginButton.click(),
]);
const accountReady = await page
.locator("#account")
.first()
.isVisible({ timeout: 4000 })
.catch(() => false);
if (!accountReady) return;
await loginViaAuthForm(page, { baseUrl: BASE, timeoutMs: UI_TIMEOUT_MS, gotoAuth: false });
await page
.waitForURL((url) => !url.toString().includes("/auth"), { timeout: UI_TIMEOUT_MS })
.catch(() => null);
}
async function main() {
+25 -7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -21,13 +22,30 @@ function mkdirp(dir) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function startPi(page) {
+25 -7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -106,13 +107,30 @@ function policyForExactPrompt() {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
await quickLoginButton.waitFor({ state: "visible", timeout: TIMEOUT });
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function requestJson(page, url, options = {}) {
+25 -7
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -41,13 +42,30 @@ async function requestJson(page, url, options = {}) {
}
async function quickLogin(page) {
await page.goto(`${BASE}/auth`, { waitUntil: "commit", timeout: TIMEOUT });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (!(await quickLoginButton.isVisible({ timeout: 4000 }).catch(() => false))) return;
await Promise.all([
page.waitForURL((url) => !url.toString().includes("/auth"), { timeout: TIMEOUT }).catch(() => null),
quickLoginButton.click(),
]);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function cleanupPreviousSmokeSession(page) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const { spawn } = require("node:child_process");
const net = require("node:net");
@@ -114,7 +115,10 @@ async function validateGateway(baseUrl) {
assert.match(authText, /data-mnote-shell="auth"/);
assert.match(authText, /账号登录/);
assert.match(authText, /邮箱或用户名/);
assert.match(authText, /测试账号快速登录/);
assert.doesNotMatch(authText, /测试账号快速登录/);
assert.doesNotMatch(authText, /data-auth-test-login/);
assert.doesNotMatch(authText, /data-test-password/);
assert.match(authText, /data-auth-submit/);
assert.doesNotMatch(authText, /隐私政策|使用\s*Google|使用\s*GitHub|第三方快捷/iu);
const root = await fetchWithTimeout(`${baseUrl}/`);
+5 -1
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const { spawn } = require("node:child_process");
const {
@@ -72,7 +73,10 @@ async function validateAuthEntry(baseUrl) {
assert.match(authText, /name="password"[^>]*type="password"|type="password"[^>]*name="password"/);
assert.match(authText, /data-auth-mode="control-plane-session"/);
assert.match(authText, /没有账号?注册/);
assert.match(authText, /测试账号快速登录/);
assert.doesNotMatch(authText, /测试账号快速登录/);
assert.doesNotMatch(authText, /data-auth-test-login/);
assert.doesNotMatch(authText, /data-test-password/);
assert.match(authText, /data-auth-submit/);
assert.doesNotMatch(authText, /隐私政策|使用\s*Google|使用\s*GitHub|第三方快捷/iu);
const authedAuth = await fetchWithTimeout(`${baseUrl}/auth`, {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs/promises");
const path = require("node:path");
@@ -16,12 +17,30 @@ function cssString(value) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function readCreatedState(page, documentId) {
@@ -1,4 +1,5 @@
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const fs = require("fs");
const os = require("os");
@@ -51,12 +52,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function waitForVisibleText(page, text) {
@@ -1,4 +1,5 @@
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
@@ -56,12 +57,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openDocument(page, root, relativePath) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs/promises");
const path = require("node:path");
@@ -12,12 +13,30 @@ const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
const OUT_DIR = path.join(ROOT, "tmp", "task438-filetree-title-md-active-reveal-smoke");
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openFileTree(page) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs/promises");
const path = require("node:path");
@@ -16,12 +17,30 @@ function cssString(value) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openFileTree(page) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs/promises");
const path = require("node:path");
@@ -16,12 +17,30 @@ function cssString(value) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openFileTree(page) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const fsp = require("node:fs/promises");
@@ -22,12 +23,30 @@ function cssString(value) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openFileTree(page) {
+24 -5
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs/promises");
const path = require("node:path");
@@ -12,12 +13,30 @@ const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
const OUT_DIR = path.join(ROOT, "tmp", "task442-trash-modal-workbench-smoke");
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function readState(page) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -47,12 +48,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openDocument(page, root, relativePath) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -50,12 +51,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function uploadLocalAsset(page, root, documentId, fileName, mimeType, bytes, kind) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const { chromium } = require("playwright");
@@ -8,12 +9,30 @@ const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000
const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function main() {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -45,12 +46,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function activatePrimaryPageTab(page) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -45,12 +46,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function uploadLocalAsset(page, root, documentId, fileName, mimeType, bytes, kind) {
+24 -5
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -45,12 +46,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function uploadLocalAsset(page, root, documentId, fileName, mimeType, bytes, kind) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -45,12 +46,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function main() {
+24 -5
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -65,12 +66,30 @@ async function uploadLocalAsset(page, root, documentId, fileName, mimeType, byte
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function main() {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
@@ -53,12 +54,30 @@ async function postTreeCommand(rootUri, action, documentId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openTrash(page, rootUri) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
@@ -32,12 +33,30 @@ function writeWorkspaceManifest(root) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
function readTrashIndex(root) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -50,12 +51,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function uploadLocalAsset(page, root, documentId, fileName, mimeType, bytes, kind) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
/**
* task473-local-folder-trash-restore-no-refresh-focus-gap-smoke.js
*
@@ -81,12 +82,30 @@ async function postTreeCommand(rootUri, action, documentId) {
* 快速登录测试账号
*/
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
/**
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
/**
* task474-local-folder-empty-trash-ui-smoke.js
*
@@ -98,12 +99,30 @@ async function postTreeCommand(rootUri, action, documentId) {
* 快速登录测试账号
*/
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
/**
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -47,12 +48,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function menuActions(page, kind) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -53,12 +54,30 @@ function writeWorkspaceManifest(root, ownerId, taskId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function activatePrimaryPageTab(page) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
/**
* task487 Local Folder Tree Live Consumer smoke
*
@@ -188,13 +189,14 @@ async function run() {
let fatalError = null;
try {
// ── 登录 ──
// ── 登录7-76 P0:标准表单)──
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS });
}
await loginViaAuthForm(page, {
baseUrl: BASE_URL,
timeoutMs: UI_TIMEOUT_MS,
gotoAuth: false,
});
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => {});
// ── Phase 1: Page Tree 视图 ──
await page.goto(documentUrl(root, "README.md", "page"), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -47,12 +48,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openDocument(page, root, relativePath) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -47,12 +48,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openDocument(page, root, relativePath) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -47,12 +48,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openDocument(page, root, relativePath) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -53,12 +54,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openDocument(page, root, relativePath) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -51,12 +52,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openDocument(page, root, relativePath) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -45,12 +46,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function uploadLocalAsset(page, root, documentId, fileName, mimeType, bytes, kind) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -63,12 +64,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function ensureTestUser(context) {
@@ -310,7 +329,8 @@ async function createAdminGrant(root, targetUserId) {
executablePath: CHROMIUM_EXECUTABLE_PATH,
}).then((browser) => browser.newContext({
extraHTTPHeaders: {
"x-mnote-actor-id": "mnote-e2e",
// 方案 Aops admin 为 mnote-admin;勿再用 mnote-e2e 冒充 admin
"x-mnote-actor-id": process.env.MNOTE_ADMIN_USERNAME || "mnote-admin",
"x-mnote-actor-type": "admin",
},
}).then((context) => ({ browser, context })));
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -45,12 +46,30 @@ function writeWorkspaceManifest(root, ownerId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function openDocument(page, root, relativePath) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
@@ -82,12 +83,30 @@ async function ensureTestUser(context) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function waitForEditor(page) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -66,12 +67,30 @@ function bridgeToolPayload(sessionId, assetId, allowedResourceIds, toolName = "m
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname !== "/auth", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function waitForOfficeReady(page, label) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
@@ -48,12 +49,30 @@ function writeWorkspaceManifest(root, ownerId, workspaceId) {
}
async function quickLogin(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await page.waitForURL((url) => url.pathname !== "/auth", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
// 7-76 P0: 标准表单登录(无测试快速登录按钮)
const base =
(typeof BASE_URL !== "undefined" && BASE_URL) ||
(typeof baseUrl !== "undefined" && baseUrl) ||
process.env.MNOTE_UI_BASE_URL ||
"http://127.0.0.1:3000";
const timeout =
(typeof UI_TIMEOUT_MS !== "undefined" && UI_TIMEOUT_MS) ||
(typeof TIMEOUT !== "undefined" && TIMEOUT) ||
30_000;
if (!String(page.url() || "").includes("/auth")) {
await page.goto(String(base).replace(/\/+$/, "") + "/auth", {
waitUntil: "commit",
timeout,
});
}
await loginViaAuthForm(page, {
baseUrl: base,
timeoutMs: timeout,
gotoAuth: false,
});
await page
.waitForURL((url) => !String(url).includes("/auth"), { timeout })
.catch(() => {});
}
async function filetreeRowState(page, relativePath) {
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
@@ -43,9 +44,7 @@ async function saveScreenshot(page, name) {
async function loginTestAccount(page) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
if (page.url().includes("/auth")) {
const quickLogin = page.locator('[data-auth-test-login]').first();
await quickLogin.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
await quickLogin.click({ timeout: UI_TIMEOUT_MS });
await loginViaAuthForm(page, { timeoutMs: typeof UI_TIMEOUT_MS !== 'undefined' ? UI_TIMEOUT_MS : undefined });
await page.waitForURL((url) => !url.toString().includes("/auth"), {
waitUntil: "commit",
timeout: UI_TIMEOUT_MS,
@@ -174,7 +174,15 @@ async function main() {
references.every((reference) => reference.sourceRootRelativePath === sourceA),
`sourcePaths scope 不应返回非 alpha 来源: ${JSON.stringify(references, null, 2).slice(0, 3000)}`,
);
assert(scoped.raw, "HTTP API 仍应保留 raw 供调试调用方使用");
// raw 字段保留形状供调试,但上游全文已剥离(防跨租户泄漏);见 rawOmitted。
assert(scoped.raw, "HTTP API 仍应保留 raw 字段(可为剥离占位)");
assert.equal(
scoped.rawOmitted === true
|| (scoped.raw && scoped.raw.omitted === true)
|| typeof scoped.raw === "object",
true,
`raw 应已剥离或为对象占位: ${JSON.stringify(scoped.raw, null, 2).slice(0, 500)}`,
);
const searched = await search(context, marker, [sourceA]);
const searchResults = Array.isArray(searched.results) ? searched.results : [];
assert.equal(searched.schema, "mnote.knowledge_rag.search_results.v1", `search schema 不正确: ${JSON.stringify(searched, null, 2).slice(0, 3000)}`);
@@ -1,6 +1,7 @@
#!/usr/bin/env node
"use strict";
const { loginViaAuthForm } = require('./lib/browser-auth-login');
const assert = require("node:assert/strict");
const fs = require("node:fs");
const http = require("node:http");
@@ -90,11 +91,12 @@ function documentUrl(baseUrl, rootUri, relativePath) {
async function quickLogin(page, baseUrl) {
await page.goto(`${baseUrl}/auth`, { waitUntil: "domcontentloaded", timeout: TIMEOUT_MS });
const quickLoginButton = page.getByRole("button", { name: "测试账号快速登录" });
if (await quickLoginButton.count()) {
await quickLoginButton.click({ timeout: TIMEOUT_MS });
await page.waitForURL((url) => url.pathname === "/", { timeout: TIMEOUT_MS });
}
await loginViaAuthForm(page, {
baseUrl,
timeoutMs: TIMEOUT_MS,
gotoAuth: false,
});
await page.waitForURL((url) => url.pathname === "/", { timeout: TIMEOUT_MS }).catch(() => {});
}
async function main() {
@@ -75,9 +75,13 @@ async function main() {
});
assert(knowledgeSearch.ok(), `资料库短 query 检索失败: ${knowledgeSearch.status()} ${await knowledgeSearch.text()}`);
const knowledgePayload = await knowledgeSearch.json();
assert((knowledgePayload.results?.length || 0) >= 8, `资料库 2 字 query 应返回段落去重后的 WeKnora 检索命中: ${JSON.stringify(knowledgePayload, null, 2).slice(0, 2000)}`);
assert((knowledgePayload.results?.length || 0) >= 8, `资料库 2 字 query 应返回段落去重后的 LightRAG 检索命中: ${JSON.stringify(knowledgePayload, null, 2).slice(0, 2000)}`);
assert(String(knowledgePayload.results?.[0]?.snippet || "").includes(SHORT_QUERY), `资料库结果未包含 ${SHORT_QUERY}: ${JSON.stringify(knowledgePayload.results?.[0], null, 2)}`);
assert(knowledgePayload.references?.some((reference) => reference.provider === "weknora" || reference.matchSource === "weknora_search"), `2 字 query 应走 WeKnora search provider: ${JSON.stringify(knowledgePayload.references?.slice(0, 3), null, 2)}`);
assert(knowledgePayload.references?.some((reference) => {
const provider = String(reference.provider || "");
const matchSource = String(reference.matchSource || "");
return provider.includes("lightrag") || matchSource.includes("lightrag") || provider === "knowledge_rag";
}), `2 字 query 应走 LightRAG search provider: ${JSON.stringify(knowledgePayload.references?.slice(0, 3), null, 2)}`);
const firstKnowledgeResult = knowledgePayload.results?.[0] || {};
const firstKnowledgeCitation = knowledgePayload.citations?.[0] || {};
assert(firstKnowledgeResult.displayQuote && firstKnowledgeResult.locatorEvidenceText, `搜索结果缺少 displayQuote/locatorEvidenceText: ${JSON.stringify(firstKnowledgeResult, null, 2)}`);
@@ -1,212 +0,0 @@
#!/usr/bin/env node
"use strict";
const assert = require("node:assert");
const fs = require("node:fs");
const path = require("node:path");
const { chromium } = require("playwright");
const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
const ROOT_PATH = process.env.MNOTE_KNOWLEDGE_RAG_ROOT_PATH || "/mnt/Data1T/Mnote_data/users/mnote-e2e/workspaces/my-space";
const ROOT_URI = process.env.MNOTE_KNOWLEDGE_RAG_ROOT_URI || `file://${ROOT_PATH}`;
const WORKSPACE_ID = process.env.MNOTE_KNOWLEDGE_RAG_WORKSPACE_ID || "local-ws:mnote-e2e:my-space";
const OUTPUT_DIR = path.join(process.cwd(), "tmp", "task79x-weknora-kb-page-browser-smoke");
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");
const FAILURE_PATH = path.join(OUTPUT_DIR, "failure.json");
const SCREENSHOT_PATH = path.join(OUTPUT_DIR, "weknora-kb-page-browser-smoke.png");
const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
|| ["/usr/bin/chromium-browser", "/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium", "/usr/bin/chromium"]
.find((candidate) => fs.existsSync(candidate));
function writeJson(filePath, payload) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
}
function locatorCount(page, selector) {
return page.locator(selector).count();
}
async function signIn(context) {
const response = await context.request.post(`${BASE_URL}/api/auth`, {
data: {
action: "auth:signIn",
args: {
provider: "password",
params: {
email: "mnote.e2e@example.com",
password: process.env.MNOTE_E2E_PASSWORD || "MnoteE2E123!",
flow: "signIn",
},
},
},
timeout: UI_TIMEOUT_MS,
});
assert(response.ok(), `登录失败: ${response.status()} ${await response.text()}`);
}
async function visible(page, selector) {
return page.locator(selector).first().isVisible().catch(() => false);
}
async function visibleCount(page, selector) {
return page.locator(selector).evaluateAll((nodes) => nodes.filter((node) => {
if (!(node instanceof HTMLElement)) return false;
const style = window.getComputedStyle(node);
const rect = node.getBoundingClientRect();
return style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
}).length).catch(() => 0);
}
async function collectPageState(page) {
const panelSelector = '[data-testid="mnote-weknora-knowledge-settings-panel"]';
const panel = page.locator(panelSelector);
const panelText = await panel.innerText({ timeout: UI_TIMEOUT_MS }).catch(() => "");
const activeTab = await page.locator('[data-kb-rag-tab][aria-selected="true"]').first().getAttribute("data-kb-rag-tab").catch(() => "");
const metaAttrs = await page.locator('[data-testid="mnote-knowledge-rag-meta"]').evaluate((node) => ({
activeProvider: node.getAttribute("data-knowledge-rag-active-provider") || "",
legacyFallbackProvider: node.getAttribute("data-knowledge-rag-legacy-fallback-provider") || "",
referenceBoundary: node.getAttribute("data-knowledge-rag-reference-boundary") || "",
})).catch(() => ({ activeProvider: "", legacyFallbackProvider: "", referenceBoundary: "" }));
const firstCard = page.locator('[data-testid="mnote-weknora-kb-card-list"] [data-knowledge-rag-action="select-kb"]').first();
const firstSourceRow = page.locator('[data-knowledge-rag-source-row="true"]').first();
const firstSearchResult = page.locator('[data-kb-rag-search-result="true"]').first();
return {
url: page.url(),
title: await page.title().catch(() => ""),
replacementMarker: await visible(page, '[data-mnote-weknora-page-replacement="true"]'),
defaultProvider: await panel.getAttribute("data-knowledge-rag-default-provider").catch(() => ""),
providerIndex: await panel.getAttribute("data-knowledge-rag-provider-index").catch(() => ""),
sourceRegistry: await panel.getAttribute("data-knowledge-rag-source-registry").catch(() => ""),
panelTextSample: panelText.slice(0, 1200),
activeTab,
metaAttrs,
counts: {
kbCards: await locatorCount(page, '[data-testid="mnote-weknora-kb-card-list"] [data-knowledge-rag-action="select-kb"]'),
createKbControls: await visibleCount(page, '[data-testid="mnote-weknora-kb-controls"], [data-knowledge-rag-action="create-kb"]'),
detailHeroes: await visibleCount(page, '[data-testid="mnote-weknora-kb-detail-hero"]'),
tabs: await locatorCount(page, '[data-kb-rag-tab]'),
sourcePlaceholdersOrRows: await locatorCount(page, '[data-testid="mnote-knowledge-rag-sources"] .wolai-page-settings-index-empty, [data-knowledge-rag-source-row="true"]'),
sourceFilters: await locatorCount(page, '[data-testid="mnote-knowledge-rag-source-filters"] [data-knowledge-rag-action="filter-sources"]'),
addDocumentEntries: await visibleCount(page, '[data-knowledge-rag-action="add-source"], [data-knowledge-rag-action="ingest"], [data-knowledge-rag-source-input]'),
processingStatusControls: await locatorCount(page, '[data-knowledge-rag-processing-count], [data-knowledge-rag-processing-status], [role="progressbar"], [data-testid="mnote-knowledge-rag-status"]'),
openReferenceControls: await locatorCount(page, '[data-knowledge-rag-action="open-source-reference"], [data-kb-rag-search-result="true"] [data-knowledge-rag-action="open-source-reference"]'),
searchControls: await visibleCount(page, '[data-testid="mnote-weknora-doc-filter-input"], [data-kb-rag-search-input], [data-kb-rag-action="search"]'),
oldSimplePanelSignals: await locatorCount(page, '.mnote-knowledge-rag-settings-panel:not(.mnote-weknora-kb-page), [data-testid="mnote-knowledge-rag-settings-panel"]:not([data-mnote-weknora-page-replacement="true"])'),
},
firstCard: {
kbId: await firstCard.getAttribute("data-knowledge-rag-kb-id").catch(() => ""),
processingCount: await firstCard.getAttribute("data-knowledge-rag-processing-count").catch(() => ""),
text: await firstCard.innerText().catch(() => ""),
},
firstSourceRow: {
sourcePath: await firstSourceRow.getAttribute("data-knowledge-rag-source-path").catch(() => ""),
kbId: await firstSourceRow.getAttribute("data-knowledge-rag-kb-id").catch(() => ""),
processingStatus: await firstSourceRow.getAttribute("data-knowledge-rag-processing-status").catch(() => ""),
openReferenceMode: await firstSourceRow.getAttribute("data-knowledge-rag-open-reference-mode").catch(() => ""),
text: await firstSourceRow.innerText().catch(() => ""),
},
firstSearchResult: {
sourcePath: await firstSearchResult.getAttribute("data-knowledge-rag-source-path").catch(() => ""),
kbId: await firstSearchResult.getAttribute("data-knowledge-rag-kb-id").catch(() => ""),
text: await firstSearchResult.innerText().catch(() => ""),
},
};
}
function evaluateAssertions(state) {
const assertions = {
openedReplacementPanel: state.replacementMarker === true,
providerIsWeKnora: state.defaultProvider === "weknora" && state.metaAttrs.activeProvider === "weknora",
notOldSimpleSettingsPanel: state.counts.oldSimplePanelSignals === 0,
hasKnowledgeBaseListCards: state.counts.kbCards > 0 || Boolean(state.firstCard.kbId),
hasCreateKbControl: state.counts.createKbControls >= 2,
hasDetailStructure: state.counts.detailHeroes > 0,
hasDocumentTabs: state.counts.tabs >= 3 && /(Documents|Wiki|Graph|文档)/.test(state.panelTextSample),
hasSourceOrTagSidebarPlaceholder: state.counts.sourcePlaceholdersOrRows > 0 && state.counts.sourceFilters >= 4,
hasSearchAndFilter: state.counts.searchControls >= 1 && state.counts.sourceFilters >= 4,
hasAddDocumentEntry: state.counts.addDocumentEntries >= 3,
hasProcessingOrStatus: state.counts.processingStatusControls > 0 && /Processing|索引|provider 状态|就绪|未就绪/.test(state.panelTextSample),
hasOpenReferenceOrCitationControl: state.counts.openReferenceControls > 0
|| state.metaAttrs.referenceBoundary === "mnote-source-registry-weknora-kb-chunk-index"
|| /引用|citation|open-reference|打开引用/i.test(state.panelTextSample),
};
const missing = Object.entries(assertions)
.filter(([, ok]) => !ok)
.map(([name]) => name);
return { assertions, missing, ok: missing.length === 0 };
}
async function main() {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
const browser = await chromium.launch({
headless: true,
...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}),
});
const context = await browser.newContext({ viewport: { width: 1440, height: 980 } });
const page = await context.newPage();
try {
await signIn(context);
const url = new URL(`${BASE_URL}/`);
url.searchParams.set("sourceKind", "local_folder");
url.searchParams.set("rootUri", ROOT_URI);
url.searchParams.set("workspaceId", WORKSPACE_ID);
url.searchParams.set("treeView", "filetree");
await page.goto(url.toString(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
await page.locator('[data-testid="mnote-knowledge-rag-settings-toggle"]').click({ timeout: UI_TIMEOUT_MS });
const panel = page.locator('[data-testid="mnote-weknora-knowledge-settings-panel"]');
await panel.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
await page.waitForFunction(() => {
const panelNode = document.querySelector('[data-testid="mnote-weknora-knowledge-settings-panel"]');
if (!(panelNode instanceof HTMLElement)) return false;
const text = panelNode.textContent || "";
const hasCard = Boolean(panelNode.querySelector('[data-testid="mnote-weknora-kb-card-list"] [data-knowledge-rag-action="select-kb"]'));
const statusSettled = !/正在读取知识库 provider 状态/.test(text);
return hasCard || statusSettled;
}, { timeout: UI_TIMEOUT_MS }).catch(() => undefined);
await page.locator('[data-kb-rag-tab="sources"]').click({ timeout: UI_TIMEOUT_MS }).catch(() => undefined);
await page.locator('[data-knowledge-rag-action="filter-sources"][data-knowledge-rag-filter="all"]').click({ timeout: UI_TIMEOUT_MS }).catch(() => undefined);
await page.locator('[data-kb-rag-tab="search"]').click({ timeout: UI_TIMEOUT_MS }).catch(() => undefined);
await page.locator('[data-kb-rag-tab="sources"]').click({ timeout: UI_TIMEOUT_MS }).catch(() => undefined);
await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true });
const state = await collectPageState(page);
const verdict = evaluateAssertions(state);
const result = {
ok: verdict.ok,
task: "task79x-weknora-kb-page-browser-smoke",
baseUrl: BASE_URL,
rootUri: ROOT_URI,
workspaceId: WORKSPACE_ID,
screenshot: SCREENSHOT_PATH,
resultPath: RESULT_PATH,
...verdict,
state,
};
writeJson(RESULT_PATH, result);
console.log(JSON.stringify(result, null, 2));
if (!result.ok) process.exit(1);
} catch (error) {
await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true }).catch(() => undefined);
const failure = {
ok: false,
task: "task79x-weknora-kb-page-browser-smoke",
baseUrl: BASE_URL,
rootUri: ROOT_URI,
workspaceId: WORKSPACE_ID,
screenshot: SCREENSHOT_PATH,
error: error.stack || error.message || String(error),
};
writeJson(FAILURE_PATH, failure);
console.error(JSON.stringify(failure, null, 2));
process.exit(1);
} finally {
await browser.close().catch(() => undefined);
}
}
main();
+4 -4
View File
@@ -1,4 +1,5 @@
const { chromium } = require('playwright');
const { loginViaAuthForm } = require('./lib/browser-auth-login');
(async () => {
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
@@ -8,12 +9,11 @@ const { chromium } = require('playwright');
const testErrors = [];
page.on('pageerror', err => testErrors.push(err.message));
// Login
// 7-76:标准表单登录(无快速登录按钮)
await page.goto('http://localhost:3000/auth', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(2000);
const qlBtn = await page.locator('text=测试账号快速登录').count();
if (qlBtn > 0) {
await page.click('text=测试账号快速登录');
if (await page.locator('#account').count()) {
await loginViaAuthForm(page, { baseUrl: 'http://localhost:3000', gotoAuth: false });
await page.waitForTimeout(3000);
}
+36 -24
View File
@@ -7,12 +7,19 @@ const DOCUMENT_UI_BASE_URL = (
BASE_URL
).replace(/\/+$/, "");
const MNOTE_WEB_BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "").replace(/\/+$/, "");
const {
DEFAULT_EMAIL,
DEFAULT_PASSWORD,
loginViaAuthForm,
loginViaAuthApi,
} = require("./lib/browser-auth-login");
const REQUEST_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_TIMEOUT_MS || 20_000);
const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
const TEST_LOGIN_BUTTON_NAME = "测试账号快速登录";
// 7-76 P0:不再依赖「测试账号快速登录」按钮;UI 走标准表单,API 走 /api/auth。
const TEST_USERNAME_PREFIX = "mnote-e2e-";
const TEST_EMAIL = "mnote.e2e@example.com";
const TEST_PASSWORD = "MnoteE2E123!";
const TEST_EMAIL = DEFAULT_EMAIL;
const TEST_PASSWORD = DEFAULT_PASSWORD;
function assert(condition, message) {
if (!condition) {
@@ -128,19 +135,12 @@ async function getViewerIdentity(requestContext) {
}
async function tryApiQuickLogin(requestContext) {
await requestJson(requestContext, "/api/auth", {
method: "POST",
data: {
action: "auth:signIn",
args: {
provider: "password",
params: {
email: TEST_EMAIL,
password: TEST_PASSWORD,
flow: "signIn",
},
},
},
// 名称保留兼容旧 smoke;实现为标准 password signIn(必要时 signUp)。
await loginViaAuthApi(requestContext, {
baseUrl: BASE_URL,
email: TEST_EMAIL,
password: TEST_PASSWORD,
timeoutMs: REQUEST_TIMEOUT_MS,
});
}
@@ -232,10 +232,10 @@ async function ensureAuthenticatedViaUi(page, requestContext) {
await page.goto(`${BASE_URL}/auth`, { waitUntil: "commit", timeout: UI_TIMEOUT_MS });
if (page.url().includes("/auth")) {
const quickLoginButton = page.getByRole("button", { name: TEST_LOGIN_BUTTON_NAME });
const accountField = page.locator("#account, input[name='account']").first();
const deadline = Date.now() + UI_TIMEOUT_MS;
while (page.url().includes("/auth") && Date.now() < deadline) {
if (await isVisible(quickLoginButton)) {
if (await isVisible(accountField)) {
break;
}
await page.waitForTimeout(200);
@@ -243,11 +243,11 @@ async function ensureAuthenticatedViaUi(page, requestContext) {
if (!page.url().includes("/auth")) {
return await waitForViewerIdentity(requestContext);
}
if (!(await isVisible(quickLoginButton))) {
if (!(await isVisible(accountField))) {
await page.reload({ waitUntil: "commit", timeout: UI_TIMEOUT_MS }).catch(() => undefined);
const retryDeadline = Date.now() + Math.min(8_000, UI_TIMEOUT_MS);
while (page.url().includes("/auth") && Date.now() < retryDeadline) {
if (await isVisible(quickLoginButton)) {
if (await isVisible(accountField)) {
break;
}
await page.waitForTimeout(200);
@@ -256,18 +256,30 @@ async function ensureAuthenticatedViaUi(page, requestContext) {
if (!page.url().includes("/auth")) {
return await waitForViewerIdentity(requestContext);
}
if (!(await isVisible(quickLoginButton))) {
throw new Error("认证页未出现测试账号快速登录按钮");
if (!(await isVisible(accountField))) {
throw new Error("认证页未出现标准登录表单(#account");
}
await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
await loginViaAuthForm(page, {
baseUrl: BASE_URL,
email: TEST_EMAIL,
password: TEST_PASSWORD,
timeoutMs: UI_TIMEOUT_MS,
gotoAuth: false,
});
try {
await waitForAuthenticatedRedirect(page, UI_TIMEOUT_MS);
} catch {
// 账号可能尚未 seed:UI 注册或 API 兜底创建后再进站。
const completedUsername = await completeUsernameSetupIfNeeded(page);
if (!completedUsername) {
const registered = await registerTestAccountIfNeeded(page);
if (!registered) {
throw new Error("测试账号登录后仍停留在 /auth,且未进入可恢复的用户名/注册流程");
try {
await tryApiQuickLogin(requestContext);
await page.goto(`${BASE_URL}/`, { waitUntil: "commit", timeout: UI_TIMEOUT_MS });
} catch {
throw new Error("标准登录后仍停留在 /auth,且注册/API 兜底失败");
}
}
}
}
+1 -1
View File
@@ -104,7 +104,7 @@ async function main() {
if (probe.status === 0) throw new Error("unreachable");
} catch (e) {
throw new Error(
`mnote-web 不可达 ${BASE}(请先 desktop:hot 且 MNOTE_VAULT=1: ${e.message || e}`
`mnote-web 不可达 ${BASE}(请先 dev:hot 且 MNOTE_VAULT=1: ${e.message || e}`
);
}