feat: OpenMesh 基础平台与 MD/PDF 转换技能
- 后端: coworker 智能体框架, WS API, 文件上传, 附件处理 - 前端: Open WebUI, 文件全量走 upload API (含 MD/TXT/JSON 等文本类) - 技能: md-to-office (pandoc + wkhtmltopdf) - 修复: 上传文件路径丢失, Agent 搜索浪费, 输出文件跑到 uploads/ - 打包: PyInstaller one-dir, 预打包 pandoc/wkhtmltopdf/chromium
This commit is contained in:
75
surfaces/gui/e2e/boot.spec.ts
Normal file
75
surfaces/gui/e2e/boot.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// Cold-boot fixes (owner-hit 2026-07-23): the splash wears the real OpenWorker mark
|
||||
// (6-point star SVG, not the ✦ text glyph that read as another product's logo), and the
|
||||
// model picker recovers when the mount-time settings fetch loses the race against the
|
||||
// sidecar boot — previously "Loading models…" stuck until the user visited Settings.
|
||||
import { expect } from "@playwright/test";
|
||||
import { test } from "./fixtures";
|
||||
|
||||
test("boot splash shows the OpenWorker star, not the sparkle glyph", async ({ page }) => {
|
||||
// Hold health long enough to observe the splash.
|
||||
await page.route("**/v1/health", async (route) => {
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
await route.fallback();
|
||||
});
|
||||
await page.goto("/");
|
||||
const mark = page.locator(".boot-mark");
|
||||
await expect(mark).toBeVisible();
|
||||
await expect(mark.locator("svg")).toBeVisible(); // the Icon logo, not a text glyph
|
||||
await expect(mark).not.toContainText("✦");
|
||||
await expect(page.getByText(/Starting OpenWorker|Restoring your session/)).toBeVisible();
|
||||
});
|
||||
|
||||
test("model picker recovers when settings fetches die during sidecar boot", async ({ page }) => {
|
||||
// Real cold-start shape: EVERY request fails until the sidecar is up (health included),
|
||||
// then everything answers. The mount-time settings fetches all lose that race and are
|
||||
// swallowed — the post-health reload must populate the picker without a Settings visit.
|
||||
let sidecarUp = false;
|
||||
await page.route("**/v1/health", async (route) => {
|
||||
await new Promise((r) => setTimeout(r, 700));
|
||||
sidecarUp = true;
|
||||
await route.fallback();
|
||||
});
|
||||
await page.route("**/v1/settings", async (route) => {
|
||||
if (route.request().method() === "GET" && !sidecarUp) {
|
||||
await route.abort();
|
||||
return;
|
||||
}
|
||||
await route.fallback();
|
||||
});
|
||||
await page.goto("/");
|
||||
await expect(page.locator(".dd").filter({ hasText: "Claude Opus 4.8" })).toBeVisible({
|
||||
timeout: 10_000,
|
||||
});
|
||||
await expect(page.getByTestId("models-loading")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("coworker picker recovers when the persona fetch dies during sidecar boot", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Same cold-start shape as above, for /v1/personas (owner-hit 2026-08-13, packaged app):
|
||||
// the mount-time fetch loses to the sidecar boot and its only other trigger is
|
||||
// PERSONAS_CHANGED, so the composer's picker stayed empty for the WHOLE session — while
|
||||
// Settings ▸ Coworkers (mounted later) listed everything and looked healthy.
|
||||
let sidecarUp = false;
|
||||
await page.route("**/v1/health", async (route) => {
|
||||
await new Promise((r) => setTimeout(r, 700));
|
||||
sidecarUp = true;
|
||||
await route.fallback();
|
||||
});
|
||||
await page.route("**/v1/personas", async (route) => {
|
||||
if (route.request().method() === "GET" && !sidecarUp) {
|
||||
await route.abort();
|
||||
return;
|
||||
}
|
||||
await route.fallback();
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
await page.getByText("New session").first().click();
|
||||
await page.getByTestId("coworker-chip").click();
|
||||
|
||||
// The menu must list real coworkers, not just its Import/Manage footer.
|
||||
const menu = page.locator(".setup-menu");
|
||||
await expect(menu.getByText("Security Coworker")).toBeVisible({ timeout: 10_000 });
|
||||
await expect(menu.getByTestId("import-coworker")).toBeVisible();
|
||||
});
|
||||
Reference in New Issue
Block a user