- Area headers get a per-area editor switcher backed by a shared AreaEditorContext; Preferences can hide it. - Animation camera area is a real navigable viewport aligned to the scene camera; Graph Editor adds channel selection, keyframe drag/ insert/delete and interpolation. - Geometry Nodes node locations round-trip through the native writer and reader, so saved graphs keep their layout. - Texture Paint can make linked library images local via a new native makeImageLocal command before packing strokes. - Compositor IMAGE and Render Layers nodes now persist to Main and execute against packed images / the last PBR render. - Spreadsheet row cap raised to 250k; Outliner gains Ctrl+A and arrow/ Home/End navigation. Co-Authored-By: Claude Code <noreply@anthropic.com>
67 lines
3.7 KiB
TypeScript
67 lines
3.7 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 page.getByText(/Engine: SceneIR/).first().waitFor({ timeout: 120_000 });
|
|
await expect.poll(async () => page.getByTestId("scene-stats").innerText(), { timeout: 120_000 }).toMatch(/Objects 6/);
|
|
}
|
|
|
|
async function workspace(page: import("@playwright/test").Page, name: string): Promise<void> {
|
|
await page.locator(".workspace-tabs button", { hasText: name }).evaluate((element) => (element as HTMLElement).click());
|
|
await page.waitForTimeout(600);
|
|
}
|
|
|
|
test("Area headers switch editor types and restore the default", async ({ page }) => {
|
|
test.setTimeout(240_000);
|
|
await openStudio(page);
|
|
const area = page.locator(".properties-area");
|
|
await expect(area.locator(".area-type-switch select")).toBeVisible();
|
|
await area.locator(".area-type-switch select").selectOption("Spreadsheet");
|
|
await expect(area).toHaveAttribute("data-editor", "Spreadsheet");
|
|
await expect(area.getByTestId("spreadsheet-view")).toBeVisible();
|
|
await area.locator(".area-type-switch select").selectOption("Properties");
|
|
await expect(area).toHaveAttribute("data-editor", "Properties");
|
|
await expect(area.locator(".area-type-switch select")).toBeVisible();
|
|
});
|
|
|
|
test("Camera viewport is real and Graph Editor edits animation keys", async ({ page }) => {
|
|
test.setTimeout(300_000);
|
|
await openStudio(page);
|
|
await workspace(page, "Animation");
|
|
await expect(page.locator(".camera-view-area canvas").first()).toBeVisible();
|
|
const key = page.getByRole("button", { name: "插入位置关键帧", exact: true });
|
|
await expect(key).toBeVisible();
|
|
const before = Number(await page.locator("main.blender-app").getAttribute("data-project-snapshot-revision") ?? "0");
|
|
await key.evaluate((element) => (element as HTMLElement).click());
|
|
const graph = page.getByTestId("graph-editor");
|
|
await graph.waitFor({ timeout: 60_000 });
|
|
await expect.poll(async () => Number(await graph.getAttribute("data-channel-count")), { timeout: 60_000 }).toBeGreaterThan(0);
|
|
await expect.poll(async () => Number(await graph.getAttribute("data-keyframe-count")), { timeout: 60_000 }).toBeGreaterThan(0);
|
|
await expect.poll(async () => Number(await page.locator("main.blender-app").getAttribute("data-project-snapshot-revision") ?? "0"), { timeout: 60_000 }).toBeGreaterThan(before);
|
|
|
|
await page.getByLabel("当前帧").fill("24");
|
|
await page.waitForTimeout(800);
|
|
const countBefore = Number(await graph.getAttribute("data-keyframe-count"));
|
|
await graph.getByRole("button", { name: "插入关键帧", exact: true }).click();
|
|
await expect.poll(async () => Number(await graph.getAttribute("data-keyframe-count")), { timeout: 60_000 }).toBeGreaterThan(countBefore);
|
|
|
|
const dot = graph.locator("circle").first();
|
|
const box = await dot.boundingBox();
|
|
if (!box) throw new Error("graph keyframe dot has no box");
|
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + box.width / 2 + 35, box.y + box.height / 2, { steps: 8 });
|
|
await page.mouse.up();
|
|
await expect.poll(async () => graph.getAttribute("data-selected-frame"), { timeout: 30_000 }).not.toBe("");
|
|
await expect(graph.getByText(/关键帧已移动/)).toBeVisible({ timeout: 30_000 });
|
|
});
|