feat: v1.1.0 账号化重构 + 三级 Webhook + 设置中心 + Vite 工具链
- 账号化:节点更名账号,/nodes -> /accounts;添加账号直达 Cloudflare OAuth,自动创建账号并同步账号下全部域名(catch-all 邮箱随同步创建) - 域名:移除手工添加;点击域名『设置 Email Workers』= 部署账号级 Email Worker + Email Routing + catch-all;支持按账号筛选 - Webhook 三级模型:邮箱 -> 域名 -> 全局,收信取第一个命中者;支持自定义请求头与 key(默认 Authorization: Bearer <key>,兼做 X-WmailHub-Signature 签名);独立 webhooks 实体与 /webhooks、/keys 页面删除,Key 管理收进设置模态窗口 - 设置中心:全局 Webhook / 默认 Node Ingress 地址 / 修改密码 / API & MCP Keys(生成、撤销、删除) - UI:Header 标题副标题紧凑化(修复溢出),LOGO 加版本号 tag(v1.1.0 取自 package.json),各列表页搜索框 - 工具链:前端构建 Bun.build -> Vite(vite 8 + @vitejs/plugin-react) - 后端:/api/settings、POST /api/domains/:id/setup、0002 迁移(投递行固化三级目标配置);MCP 去 list_webhooks(5 工具);迁移执行器加 _migrations 记录 - 测试:44/44(新增 settings 套件、webhook 三级优先级/SSRF/签名);UI 冒烟 11/11
This commit is contained in:
+35
-18
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* UI 冒烟测试(Playwright headless):
|
||||
* 登录 admin/admin → 概览/节点/域名/邮箱/邮件/Webhooks/Keys 各页渲染 → 截图。
|
||||
* 登录 admin/admin → 概览/账号/域名/邮箱/邮件 各页渲染 → 设置模态窗口 → 生成 Key → 截图。
|
||||
* bun tests/ui-smoke.ts
|
||||
*/
|
||||
|
||||
@@ -42,45 +42,62 @@ await page.click("button[type=submit]");
|
||||
await page.waitForURL(`${BASE}/`, { timeout: 8000 });
|
||||
await page.waitForSelector("text=概览", { timeout: 8000 });
|
||||
check("登录成功进入概览页", true);
|
||||
// 品牌区版本号 tag(v1.1.0,来源 package.json)
|
||||
const versionTag = await page.locator(".brand-version").textContent();
|
||||
check("LOGO 版本号 tag 显示", Boolean(versionTag && /^v\d+\.\d+\.\d+$/.test(versionTag.trim())), versionTag?.trim());
|
||||
await page.waitForTimeout(600);
|
||||
await page.screenshot({ path: `${SHOT_DIR}/02-dashboard.png` });
|
||||
|
||||
// 3. 各页面导航渲染
|
||||
const pages = [
|
||||
{ path: "/nodes", expect: "节点管理", shot: "03-nodes.png" },
|
||||
{ path: "/domains", expect: "域名管理", shot: "04-domains.png" },
|
||||
{ path: "/mailboxes", expect: "邮箱管理", shot: "05-mailboxes.png" },
|
||||
{ path: "/accounts", expect: "账号", shot: "03-accounts.png" },
|
||||
{ path: "/domains", expect: "域名", shot: "04-domains.png" },
|
||||
{ path: "/mailboxes", expect: "邮箱", shot: "05-mailboxes.png" },
|
||||
{ path: "/emails", expect: "邮件", shot: "06-emails.png" },
|
||||
{ path: "/webhooks", expect: "Webhooks", shot: "07-webhooks.png" },
|
||||
{ path: "/keys", expect: "API / MCP Keys", shot: "08-keys.png" },
|
||||
];
|
||||
for (const p of pages) {
|
||||
await page.goto(`${BASE}${p.path}`, { waitUntil: "domcontentloaded" });
|
||||
await page.waitForSelector(`text=${p.expect}`, { timeout: 8000 });
|
||||
await page.waitForTimeout(500);
|
||||
const hasTable = (await page.locator(".ant-table").count()) > 0;
|
||||
check(`页面 ${p.path} 渲染${hasTable ? "(含表格)" : ""}`, true);
|
||||
const hasSearch = (await page.locator("input[placeholder*='搜索']").count()) > 0;
|
||||
check(`页面 ${p.path} 渲染${hasTable ? "(含表格)" : ""}${hasSearch ? "(含搜索框)" : ""}`, hasTable);
|
||||
await page.screenshot({ path: `${SHOT_DIR}/${p.shot}` });
|
||||
}
|
||||
|
||||
// 4. 邮件详情(点击第一行)
|
||||
// 4. 域名详情抽屉(点击第一行:Email Workers + Webhook 配置)
|
||||
await page.goto(`${BASE}/domains`, { waitUntil: "domcontentloaded" });
|
||||
await page.waitForSelector(".ant-table-row", { timeout: 8000 });
|
||||
await page.locator(".ant-table-row").first().click();
|
||||
await page.waitForSelector(".ant-drawer-open", { timeout: 8000 });
|
||||
await page.waitForSelector("text=域名级 Webhook", { timeout: 8000 });
|
||||
check("域名详情抽屉(Email Workers + Webhook 配置)", true);
|
||||
await page.screenshot({ path: `${SHOT_DIR}/07-domain-drawer.png` });
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
// 5. 邮件详情(点击第一行)
|
||||
await page.goto(`${BASE}/emails`, { waitUntil: "domcontentloaded" });
|
||||
await page.waitForSelector(".ant-table-row", { timeout: 8000 });
|
||||
await page.locator(".ant-table-row").first().click();
|
||||
await page.waitForSelector(".ant-drawer-open", { timeout: 8000 });
|
||||
const drawerText = await page.locator(".ant-drawer-title").textContent();
|
||||
check("邮件详情抽屉打开", Boolean(drawerText && drawerText.includes("端到端")));
|
||||
await page.screenshot({ path: `${SHOT_DIR}/09-email-detail.png` });
|
||||
check("邮件详情抽屉打开", Boolean(drawerText && drawerText.includes("WmailHub")));
|
||||
await page.screenshot({ path: `${SHOT_DIR}/08-email-detail.png` });
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
// 5. 生成 API Key 流程
|
||||
await page.goto(`${BASE}/keys`, { waitUntil: "domcontentloaded" });
|
||||
await page.waitForSelector("text=生成 Key", { timeout: 8000 });
|
||||
await page.click("button:has-text('生成 Key')");
|
||||
await page.click(".ant-dropdown-menu-item:has-text('生成 API Key')");
|
||||
// 6. 设置模态窗口:基本设置 + 生成 MCP Key
|
||||
await page.locator("button[aria-label='设置']").click();
|
||||
await page.waitForSelector(".ant-modal:has-text('全局 Webhook')", { timeout: 8000 });
|
||||
check("设置模态窗口打开(全局 Webhook / Ingress / 密码)", true);
|
||||
await page.screenshot({ path: `${SHOT_DIR}/09-settings-general.png` });
|
||||
|
||||
await page.click(".ant-tabs-tab:has-text('MCP Keys')");
|
||||
await page.waitForSelector("button:has-text('生成 MCP Key')", { timeout: 8000 });
|
||||
await page.click("button:has-text('生成 MCP Key')");
|
||||
await page.waitForSelector(".ant-modal:has-text('仅显示一次')", { timeout: 8000 });
|
||||
const keyText = await page.locator(".ant-modal .mono").first().textContent();
|
||||
check("生成 API Key 弹窗(wmh_ 32 位)", Boolean(keyText && keyText.startsWith("wmh_") && keyText.trim().length === 32), keyText?.trim());
|
||||
await page.screenshot({ path: `${SHOT_DIR}/10-key-created.png` });
|
||||
const keyText = await page.locator(".ant-modal:has-text('仅显示一次') .mono").first().textContent();
|
||||
check("生成 MCP Key 弹窗(wmh_ 32 位)", Boolean(keyText && keyText.trim().startsWith("wmh_") && keyText.trim().length === 32), keyText?.trim());
|
||||
await page.screenshot({ path: `${SHOT_DIR}/10-mcp-key-created.png` });
|
||||
|
||||
await browser.close();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user