Files
workinf_Blender_Wasm/web/tests/e2e/web-manual-ui-gaps.spec.ts
mes123456 b932db7642 feat(manual): add the illustrated Web Blender HTML user manual
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>
2026-11-13 11:58:40 +08:00

50 lines
2.8 KiB
TypeScript

import { expect, test } from "@playwright/test";
import path from "node:path";
import { fileURLToPath } from "node:url";
const HERE = path.dirname(fileURLToPath(import.meta.url));
const STUDIO = path.resolve(HERE, "../../app/public/tutorial/pbr/studio.blend");
test.use({ viewport: { width: 1600, height: 900 } });
async function openStudio(page: import("@playwright/test").Page): Promise<void> {
await page.goto("/?ui=web");
await page.locator("main.blender-app").waitFor({ timeout: 120_000 });
await page.locator('[data-testid="blend-file-input"]').setInputFiles(STUDIO);
await expect.poll(async () => page.getByTestId("scene-stats").innerText(), { timeout: 120_000 }).toMatch(/Objects 6/);
await page.waitForTimeout(700);
}
test("Properties tabs jump to object, modifier and material sections", async ({ page }) => {
test.setTimeout(240_000);
await openStudio(page);
const properties = page.locator(".properties-area");
const modifierTab = properties.locator('button[title="修改器属性"]');
await modifierTab.evaluate((element) => (element as HTMLElement).click());
await expect(modifierTab).toHaveAttribute("aria-selected", "true");
await expect.poll(async () => properties.locator(".modifier-panel").evaluate((element) => {
const box = element.getBoundingClientRect();
const area = element.closest(".properties-area")!.getBoundingClientRect();
return box.top >= area.top - 2 && box.top < area.bottom;
})).toBe(true);
const materialTab = properties.locator('button[title="材质属性"]');
await materialTab.evaluate((element) => (element as HTMLElement).click());
await expect(properties.getByTestId("material-slots-section")).toBeVisible();
await expect(materialTab).toHaveAttribute("aria-selected", "true");
});
test("Editor header ⋮ opens the same area menu as the floating switcher", async ({ page }) => {
test.setTimeout(240_000);
await openStudio(page);
await page.locator(".workspace-tabs button", { hasText: "Scripting" }).evaluate((element) => (element as HTMLElement).click());
await page.waitForTimeout(600);
const headerMenu = page.locator(".viewport-area .editor-header .area-menu button").first();
await expect(headerMenu).toBeVisible({ timeout: 30_000 });
await headerMenu.evaluate((element) => (element as HTMLElement).click());
await page.getByRole("menuitem", { name: "垂直分割(左右)", exact: true }).click();
await expect(page.locator("main.blender-app")).toHaveAttribute("data-area-layout", "custom");
await page.locator(".workspace-grid > .area-frame .area-type-switch .area-menu button").first().evaluate((element) => (element as HTMLElement).click());
await page.getByRole("menuitem", { name: "重置工作区布局", exact: true }).click();
await expect(page.locator("main.blender-app")).toHaveAttribute("data-area-layout", "default");
});