#!/usr/bin/env node "use strict"; const assert = require("node:assert"); const fs = require("node:fs"); const { spawn } = require("node:child_process"); const { chromium } = require("playwright"); const { findFreePort, waitForGateway, } = require("./task114-rust-web-gateway-entry-smoke.js"); const UI_TIMEOUT_MS = Number(process.env.UI_TIMEOUT_MS || 10_000); function chromiumExecutablePath() { return [ process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || "", "/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium", "/usr/bin/chromium", "/usr/bin/chromium-browser", ].find((candidate) => candidate && fs.existsSync(candidate)) || undefined; } function startGateway(port) { return spawn("cargo", ["run", "-q", "-p", "mnote-web", "--bin", "mnote-web"], { cwd: "/mnt/Data1T/mnote/rust", env: { ...process.env, MNOTE_WEB_ALLOW_DEV_FIXTURES: "1", MNOTE_WEB_BIND: `127.0.0.1:${port}`, MNOTE_WEB_PUBLIC_BIND: "127.0.0.1:3000", MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT: "1", MNOTE_WEB_LEGACY_NEXT_BASE_URL: "http://127.0.0.1:3100", }, stdio: ["ignore", "pipe", "pipe"], }); } async function main() { let baseUrl = (process.env.MNOTE_UI_BASE_URL || "").replace(/\/+$/, ""); let gateway = null; if (!baseUrl) { const port = await findFreePort(); baseUrl = `http://127.0.0.1:${port}`; gateway = startGateway(port); await waitForGateway(baseUrl); } const executablePath = chromiumExecutablePath(); const browser = await chromium.launch({ headless: true, ...(executablePath ? { executablePath } : {}), }); try { const authPage = await browser.newPage(); await authPage.goto(`${baseUrl}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); await authPage.locator('[data-testid="mnote-auth-page"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); assert(await authPage.locator('#account').isVisible(), "登录态应显示邮箱或用户名输入框"); assert(!(await authPage.locator('#email').isVisible()), "登录态不应显示注册邮箱输入框"); await authPage.locator('[data-auth-switch]').click(); await authPage.locator('#email').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); assert(await authPage.locator('#username').isVisible(), "注册态应显示用户名输入框"); assert(!(await authPage.locator('#account').isVisible()), "注册态不应显示登录账号输入框"); await authPage.close(); const userContext = await browser.newContext({ extraHTTPHeaders: { "x-mnote-actor-id": "user_profile_smoke", "x-mnote-actor-type": "user", }, }); const userPage = await userContext.newPage(); await userPage.goto(baseUrl, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); await userPage.locator('[data-testid="mnote-account-menu-trigger"]').click(); await userPage.locator('[data-testid="mnote-account-menu"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); assert.equal(await userPage.locator('[data-testid="mnote-account-access-policy"]').count(), 0, "授权管理入口应已退役"); await userPage.locator('[data-testid="mnote-account-ai-management"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await userPage.locator('[data-testid="mnote-account-profile"]').click(); await userPage.locator('[data-testid="mnote-profile-dialog"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const profileText = await userPage.locator('[data-testid="mnote-profile-dialog"]').innerText(); assert(profileText.includes("user_profile_smoke"), "个人信息弹窗应显示完整用户 ID"); await userPage.goto(`${baseUrl}/user/access-policy`, { waitUntil: "commit", timeout: UI_TIMEOUT_MS }); await userPage.waitForURL((url) => url.pathname === "/user/ai" && url.hash === "#ai-admin-access", { timeout: UI_TIMEOUT_MS }); await userPage.locator("#ai-admin-access.is-active").waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const userAccessText = await userPage.locator("#ai-admin-access").innerText(); assert(userAccessText.includes("目录权限"), "旧用户授权页应跳转到用户 AI 设置的目录权限面板"); await userContext.close(); const adminContext = await browser.newContext({ extraHTTPHeaders: { "x-mnote-actor-id": "admin_profile_smoke", "x-mnote-actor-type": "admin", }, }); const adminPage = await adminContext.newPage(); await adminPage.goto(baseUrl, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS }); await adminPage.locator('[data-testid="mnote-account-menu-trigger"]').click(); await adminPage.locator('[data-testid="mnote-account-menu"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); assert.equal(await adminPage.locator('[data-testid="mnote-account-access-policy"]').count(), 0, "管理员账号菜单不应再出现授权管理入口"); await adminPage.locator('[data-testid="mnote-account-ai-management"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); await adminPage.goto(`${baseUrl}/admin/access-policy`, { waitUntil: "commit", timeout: UI_TIMEOUT_MS }); await adminPage.waitForURL((url) => url.pathname === "/admin/ai" && url.hash === "#ai-admin-access", { timeout: UI_TIMEOUT_MS }); await adminPage.locator("#ai-admin-access.is-active").waitFor({ state: "visible", timeout: UI_TIMEOUT_MS }); const adminAccessText = await adminPage.locator("#ai-admin-access").innerText(); assert(adminAccessText.includes("目录权限"), "旧管理员授权页应跳转到管理员 AI 设置的目录权限面板"); await adminContext.close(); console.log(JSON.stringify({ ok: true, task: "task489-auth-profile-access-ui", baseUrl }, null, 2)); } finally { await browser.close().catch(() => {}); if (gateway) { gateway.kill("SIGTERM"); setTimeout(() => gateway.kill("SIGKILL"), 2_000).unref(); } } } main().catch((error) => { console.error(error); process.exitCode = 1; });