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 PBR_FIXTURE = path.resolve(HERE, "../../../tests/files/web/m12_glb_main_v1/pbr.blend"); test.use({ viewport: { width: 1600, height: 1000 } }); const objectCount = async (page: import("@playwright/test").Page): Promise => { const text = await page.getByTestId("scene-stats").innerText(); return Number(/Objects (\d+)/.exec(text)?.[1] ?? "0"); }; async function openShading(page: import("@playwright/test").Page) { await page.goto("/?ui=web"); await page.locator('[data-testid="blend-file-input"]').setInputFiles(PBR_FIXTURE); await expect(page.locator(".blender-app")).toHaveAttribute("data-project-id", /.+/, { timeout: 60_000 }); await expect(page.getByText(/Engine: SceneIR/).first()).toBeVisible({ timeout: 60_000 }); const webTools = page.locator('[data-testid="native-web-tools-toggle"]'); if (await webTools.count()) await webTools.click(); await page.getByRole("button", { name: "Shading", exact: true }).click({ force: true }); await expect(page.locator(".pbr-asset-library")).toBeVisible({ timeout: 30_000 }); await expect(page.locator(".pbr-asset-card")).toHaveCount(20, { timeout: 60_000 }); } test("PBR asset library seeds OPFS, appends a model, assigns a material and renders", async ({ page }) => { test.setTimeout(300_000); await openShading(page); await expect.poll(async () => page.locator(".pbr-asset-thumb img").evaluateAll((images) => images.filter((image) => (image as HTMLImageElement).naturalWidth > 0).length), { timeout: 30_000 }).toBe(20); const before = await objectCount(page); await page.locator(".pbr-asset-card", { hasText: "PBR Cube" }).first().dblclick(); await expect.poll(() => objectCount(page), { timeout: 30_000 }).toBeGreaterThan(before); await page.locator(".pbr-asset-card", { hasText: "PBR Metal Brushed" }).first().dblclick(); await expect.poll(async () => page.locator('select[aria-label^="材质槽"]').first().inputValue(), { timeout: 30_000 }) .toBe("material:PBR Metal Brushed"); await expect.poll(async () => page.locator(".material-preview-status").innerText(), { timeout: 30_000 }).toContain("COMPILED"); await page.getByRole("button", { name: "Rendering", exact: true }).click(); await page.getByRole("button", { name: "渲染 PBR", exact: true }).click(); await expect.poll(async () => page.locator(".pbr-render-result img").count(), { timeout: 90_000 }).toBe(1); await expect.poll(async () => page.locator(".pbr-render-result img").first().evaluate((image) => (image as HTMLImageElement).naturalWidth), { timeout: 30_000 }).toBeGreaterThan(0); }); test("seeded PBR asset catalog and previews survive a page reload", async ({ page }) => { test.setTimeout(240_000); await openShading(page); await page.reload(); await expect(page.locator(".blender-app")).toBeVisible({ timeout: 60_000 }); const persisted = await page.evaluate(async () => { const { StorageClient } = await import("/src/storage/StorageClient.ts"); const client = new StorageClient(); try { const catalog = await client.getAssetCatalog(); const preview = catalog.manifest?.assets.find((asset) => asset.preview)?.preview; const bytes = preview ? await client.readAssetCatalogPreview("asset-library", preview.sha256) : null; return { assets: catalog.manifest?.assets.length ?? 0, revision: catalog.manifest?.revision ?? 0, seedId: catalog.seed?.seedId ?? "", previewBytes: bytes?.data.byteLength ?? 0, }; } finally { client.terminate(); } }); expect(persisted.assets).toBe(20); expect(persisted.seedId).toBe("pbr-essentials-v1"); expect(persisted.previewBytes).toBeGreaterThan(0); });