Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
82 lines
6.4 KiB
TypeScript
82 lines
6.4 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import path from "node:path";
|
|
|
|
const basicBlend = path.resolve(import.meta.dirname, "../../../tests/files/web/basic_scene.blend");
|
|
|
|
for (const offscreen of [false, true]) {
|
|
test(`M7-15 ${offscreen ? "Offscreen" : "main-thread"} viewport uses the shared camera and selection contract`, async ({ page }) => {
|
|
await page.setViewportSize({ width: 1280, height: 720 });
|
|
await page.goto(offscreen ? "/?offscreen=1" : "/");
|
|
await page.setInputFiles("[data-testid=blend-file-input]", basicBlend);
|
|
await expect(page.getByText("BasicCube", { exact: true })).toBeVisible({ timeout: 20_000 });
|
|
const canvas = page.locator("canvas.viewport-canvas");
|
|
await expect(canvas).toHaveAttribute("data-renderer-backend", offscreen ? "offscreen-worker" : "webgl-pbr");
|
|
if (offscreen) await expect.poll(async () => Number(await canvas.getAttribute("data-renderer-pixels") ?? "0"), { timeout: 20_000 }).toBeGreaterThan(0);
|
|
|
|
const initial = await canvas.evaluate((element) => ({
|
|
position: element.getAttribute("data-camera-position"),
|
|
target: element.getAttribute("data-camera-target"),
|
|
yaw: element.getAttribute("data-camera-yaw"),
|
|
pitch: element.getAttribute("data-camera-pitch"),
|
|
distance: element.getAttribute("data-camera-distance"),
|
|
}));
|
|
expect(initial.position).toBe("4.219781,-4.219781,3.658811");
|
|
expect(initial.target).toBe("0,0,0");
|
|
expect(initial.yaw).toBe("-0.785398");
|
|
expect(initial.pitch).toBe("0.550000");
|
|
expect(initial.distance).toBe("7.000000");
|
|
|
|
await canvas.evaluate((element, useOffscreen) => {
|
|
const bounds = element.getBoundingClientRect();
|
|
const x = bounds.left + bounds.width / 2;
|
|
const y = bounds.top + bounds.height / 2;
|
|
element.dispatchEvent(new MouseEvent("click", { bubbles: true, clientX: x, clientY: y }));
|
|
if (useOffscreen) {
|
|
element.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, clientX: x, clientY: y, pointerId: 11, buttons: 1 }));
|
|
element.dispatchEvent(new PointerEvent("pointerup", { bubbles: true, clientX: x, clientY: y, pointerId: 11, buttons: 0 }));
|
|
}
|
|
}, offscreen);
|
|
await expect(page.locator(".blender-app")).toHaveAttribute("data-selected-object-ids", /.+/);
|
|
|
|
await canvas.evaluate((element) => element.dispatchEvent(new WheelEvent("wheel", { bubbles: true, cancelable: true, deltaY: 120 })));
|
|
await expect.poll(async () => await canvas.getAttribute("data-camera-distance"), { timeout: 5_000 }).not.toBe(initial.distance);
|
|
const zoomed = await canvas.getAttribute("data-camera-distance");
|
|
expect(Number(zoomed)).toBeCloseTo(7 * Math.exp(0.12), 4);
|
|
});
|
|
}
|
|
|
|
test("M7-15 main and Offscreen camera state stays identical for the same orbit input", async ({ browser }) => {
|
|
const states: Array<Record<string, string | null>> = [];
|
|
for (const offscreen of [false, true]) {
|
|
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
|
|
await page.goto(offscreen ? "/?offscreen=1" : "/");
|
|
const canvas = page.locator("canvas.viewport-canvas");
|
|
if (offscreen) await expect.poll(async () => Number(await canvas.getAttribute("data-renderer-pixels") ?? "0"), { timeout: 20_000 }).toBeGreaterThan(0);
|
|
await canvas.evaluate((element) => element.dispatchEvent(new WheelEvent("wheel", { bubbles: true, cancelable: true, deltaY: -80 })));
|
|
await expect.poll(async () => await canvas.getAttribute("data-camera-distance"), { timeout: 5_000 }).not.toBe("7.000000");
|
|
const bounds=await canvas.boundingBox();if(!bounds)throw new Error('Missing viewport');
|
|
await page.mouse.move(bounds.x+bounds.width*.4,bounds.y+bounds.height*.5);
|
|
await page.mouse.down({button:'middle'});await page.mouse.move(bounds.x+bounds.width*.45,bounds.y+bounds.height*.54,{steps:6});await page.mouse.up({button:'middle'});
|
|
await page.keyboard.down('Shift');await page.mouse.down({button:'middle'});await page.mouse.move(bounds.x+bounds.width*.49,bounds.y+bounds.height*.56,{steps:6});await page.mouse.up({button:'middle'});await page.keyboard.up('Shift');
|
|
states.push(await canvas.evaluate((element) => ({ position: element.getAttribute("data-camera-position"), target: element.getAttribute("data-camera-target"), yaw: element.getAttribute("data-camera-yaw"), pitch: element.getAttribute("data-camera-pitch"), distance: element.getAttribute("data-camera-distance") })));
|
|
await page.close();
|
|
}
|
|
expect(states[0]).toEqual(states[1]);
|
|
});
|
|
|
|
for(const offscreen of [false,true])test(`Blender navigation preserves the Main and uses Z-up axis views (${offscreen?'offscreen':'main'})`,async({page})=>{
|
|
await page.goto(offscreen?'/?offscreen=1':'/');await page.getByTestId('blend-file-input').setInputFiles(basicBlend);
|
|
await expect(page.getByTestId('engine-status')).toContainText('SceneIR');const canvas=page.locator('canvas.viewport-canvas');
|
|
const revision=await page.locator('main.blender-app').getAttribute('data-project-snapshot-revision');
|
|
const initial=await canvas.getAttribute('data-camera-position'),bounds=await canvas.boundingBox();if(!bounds)throw new Error('Missing canvas');
|
|
await page.mouse.move(bounds.x+bounds.width*.4,bounds.y+bounds.height*.4);await page.mouse.down();await page.mouse.move(bounds.x+bounds.width*.45,bounds.y+bounds.height*.45,{steps:5});await page.mouse.up();
|
|
await expect(canvas).toHaveAttribute('data-camera-position',initial!);
|
|
await canvas.focus();await page.keyboard.press('Numpad1');await expect(canvas).toHaveAttribute('data-camera-view','FRONT');await expect(canvas).toHaveAttribute('data-camera-projection','ORTHOGRAPHIC');
|
|
const front=(await canvas.getAttribute('data-camera-position'))!.split(',').map(Number);expect(front[0]).toBeCloseTo(0,5);expect(front[1]).toBeLessThan(0);expect(front[2]).toBeCloseTo(0,5);
|
|
await page.keyboard.press('Numpad7');await expect(canvas).toHaveAttribute('data-camera-view','TOP');
|
|
const top=(await canvas.getAttribute('data-camera-position'))!.split(',').map(Number);expect(top[2]).toBeGreaterThan(0);expect(top[0]).toBeCloseTo(0,5);expect(top[1]).toBeCloseTo(0,4);
|
|
await page.keyboard.press('Numpad5');await expect(canvas).toHaveAttribute('data-camera-projection','PERSPECTIVE');
|
|
await page.getByRole('button',{name:'显示全部',exact:true}).click();await expect(canvas).not.toHaveAttribute('data-camera-target','0,0,0');
|
|
await expect(page.locator('main.blender-app')).toHaveAttribute('data-project-snapshot-revision',revision!);
|
|
});
|