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>
50 lines
2.8 KiB
TypeScript
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");
|
|
});
|