A single-page 28-chapter manual generated from manual.json covers the 11 workspaces, all React editors, OPFS save/reopen, explicit local downloads, import/export routes, preferences/keymap and the capability matrix. It reuses verified course screenshots and adds 14 fresh Chromium captures for shell, storage, area, camera, preferences, IO and troubleshooting chapters. Manual-driven fixes: Save As now states OPFS-only, GLTF import is restored, quick toolbar honors its preference, startup workspace is applied, Properties tabs jump to sections, Outliner fallback search filters, and the editor-header ⋮ opens the area menu. Help and the tutorial landing link the new manual; the PDF remains as an archive. Co-Authored-By: Claude Code <noreply@anthropic.com>
83 lines
4.7 KiB
TypeScript
83 lines
4.7 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
test.use({ viewport: { width: 1600, height: 900 } });
|
|
|
|
async function openPreferences(page: import("@playwright/test").Page): Promise<void> {
|
|
await page.getByRole("button", { name: "编辑", exact: true }).click();
|
|
await page.getByRole("menuitem", { name: "偏好设置", exact: true }).click();
|
|
await page.getByTestId("preferences-window").waitFor({ timeout: 30_000 });
|
|
}
|
|
|
|
test("Preferences persist through OPFS and keymap rebinding takes effect", async ({ page }) => {
|
|
test.setTimeout(240_000);
|
|
await page.goto("/?ui=web");
|
|
await page.locator("main.blender-app").waitFor({ timeout: 120_000 });
|
|
await openPreferences(page);
|
|
await expect(page.getByTestId("preferences-window").getByRole("button", { name: "界面", exact: true })).toHaveClass(/active/);
|
|
await expect(page.getByRole("dialog", { name: "Web Blender 偏好设置" }).locator("nav button")).toHaveCount(9);
|
|
const statusToggle = page.getByLabel("显示状态栏", { exact: true });
|
|
if (await statusToggle.isChecked()) await statusToggle.uncheck();
|
|
await page.getByRole("button", { name: "保存偏好", exact: true }).click();
|
|
await expect(page.getByTestId("preferences-window").locator("header output")).toContainText("已保存到 OPFS", { timeout: 30_000 });
|
|
|
|
await page.getByRole("button", { name: "键位", exact: true }).click();
|
|
const row = page.locator('tr[data-action="edit.operatorSearch"]');
|
|
await row.getByRole("button", { name: "录制", exact: true }).click();
|
|
await page.keyboard.press("Shift+F3");
|
|
await expect(row.locator("code")).toHaveText("Shift + F3");
|
|
await page.getByRole("button", { name: "保存偏好", exact: true }).click();
|
|
await page.getByRole("button", { name: "关闭偏好设置", exact: true }).click();
|
|
await expect(page.locator("main.blender-app")).toHaveAttribute("data-hide-status", "true");
|
|
|
|
await page.keyboard.press("F3");
|
|
await expect(page.getByLabel("搜索 Blender 操作")).toHaveCount(0);
|
|
await page.keyboard.press("Shift+F3");
|
|
await expect(page.getByLabel("搜索 Blender 操作")).toBeVisible({ timeout: 30_000 });
|
|
await page.keyboard.press("Escape");
|
|
|
|
await page.reload();
|
|
await page.locator("main.blender-app").waitFor({ timeout: 120_000 });
|
|
await openPreferences(page);
|
|
await expect(page.getByLabel("显示状态栏", { exact: true })).not.toBeChecked();
|
|
await page.getByRole("button", { name: "键位", exact: true }).click();
|
|
await expect(page.locator('tr[data-action="edit.operatorSearch"] code')).toHaveText("Shift + F3");
|
|
const stored = await page.evaluate(async () => {
|
|
const root = await navigator.storage.getDirectory();
|
|
const directory = await root.getDirectoryHandle("settings");
|
|
const file = await (await directory.getFileHandle("web-interface.json")).getFile();
|
|
return JSON.parse(await file.text());
|
|
});
|
|
expect(stored.preferences.keymap.bindings["edit.operatorSearch"]).toBe("Shift+F3");
|
|
expect(stored.preferences.interface.showStatus).toBe(false);
|
|
});
|
|
|
|
test("Keymap conflict is rejected and unsupported preference rows are marked", async ({ page }) => {
|
|
test.setTimeout(180_000);
|
|
await page.goto("/?ui=web");
|
|
await page.locator("main.blender-app").waitFor({ timeout: 120_000 });
|
|
await openPreferences(page);
|
|
await page.getByRole("button", { name: "键位", exact: true }).click();
|
|
await page.locator('tr[data-action="edit.undo"]').getByRole("button", { name: "录制", exact: true }).click();
|
|
await page.keyboard.press("Control+S");
|
|
await expect(page.getByTestId("preferences-window").locator("header output")).toContainText("冲突");
|
|
await page.getByRole("button", { name: "视口", exact: true }).click();
|
|
await expect(page.locator('.preference-row[data-setting="viewport.gizmoSize"]')).toHaveAttribute("data-unsupported", "true");
|
|
});
|
|
|
|
test("Quick toolbar and startup workspace preferences take effect", async ({ page }) => {
|
|
test.setTimeout(240_000);
|
|
await page.goto("/?ui=web");
|
|
await page.locator("main.blender-app").waitFor({ timeout: 120_000 });
|
|
await expect(page.locator(".legacy-quick-toolbar")).toBeVisible();
|
|
await openPreferences(page);
|
|
await page.getByLabel("显示 Web 快捷操作栏", { exact: true }).uncheck();
|
|
await page.getByLabel("启动工作区", { exact: true }).selectOption("Animation");
|
|
await page.getByRole("button", { name: "保存偏好", exact: true }).click();
|
|
await page.getByRole("button", { name: "关闭偏好设置", exact: true }).click();
|
|
await expect(page.locator(".legacy-quick-toolbar")).toBeHidden();
|
|
await page.reload();
|
|
await page.locator("main.blender-app").waitFor({ timeout: 120_000 });
|
|
await expect.poll(async () => page.locator("main.blender-app").getAttribute("data-workspace"), { timeout: 60_000 }).toBe("Animation");
|
|
await expect(page.locator(".legacy-quick-toolbar")).toBeHidden();
|
|
});
|