import { expect, test } from "@playwright/test"; import fs from "node:fs/promises"; import path from "node:path"; const basicBlend = path.resolve(import.meta.dirname, "../../../tests/files/web/basic_scene.blend"); const materialBlend = path.resolve(import.meta.dirname, "../../../tests/files/web/attribute_scene.blend"); test("M7-17 keeps Worker detail out of the UI and exports it in a diagnostics report", async ({ page }) => { await page.goto("/?worker-fault=engine"); await expect(page.getByTestId("engine-status")).toContainText("ready, open a .blend file", { timeout: 20_000 }); const app = page.locator("main.blender-app"); await page.getByTestId("blend-file-input").setInputFiles(basicBlend); await expect(page.getByText("BasicCube", { exact: true })).toBeVisible(); const revision = Number(await app.getAttribute("data-current-main-revision")); await page.getByTestId("inject-worker-crash").click(); await expect(app).toHaveAttribute("data-worker-fault-code", "WORKER_TERMINATED"); await expect(app).toHaveAttribute("data-last-diagnostic-code", "ENGINE_WORKER_TERMINATED"); await expect(page.getByTestId("engine-status")).toHaveText("Engine: Worker stopped; project remains available"); await expect(page.locator("body")).not.toContainText("WORKER_CRASH_INJECTED"); const downloadPromise = page.waitForEvent("download"); await page.getByTestId("export-diagnostics").click(); const download = await downloadPromise; expect(download.suggestedFilename()).toBe("blender-web-diagnostics.json"); const reportPath = await download.path(); expect(reportPath).not.toBeNull(); const report = JSON.parse(await fs.readFile(reportPath!, "utf8")); expect(report).toMatchObject({ schemaVersion: 1, product: "Web Blender Modeler V1", runtime: { url: expect.stringMatching(/^http:\/\/127\.0\.0\.1:\d+\/$/), userAgent: expect.any(String), language: expect.any(String), crossOriginIsolated: true, }, project: { projectId: "basic_scene", revision }, }); expect(report.generatedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/); expect(report.entries).toHaveLength(1); expect(report.entries[0]).toMatchObject({ schemaVersion: 1, sequence: 1, area: "ENGINE", code: "ENGINE_WORKER_TERMINATED", summary: "Engine: Worker stopped; project remains available", sourceCode: "WORKER_TERMINATED", context: { projectId: "basic_scene", revision }, }); expect(report.entries[0].detail).toContain("WORKER_CRASH_INJECTED"); }); test("M7-17 records open and import details behind stable user messages", async ({ page }) => { await page.goto("/"); await expect(page.getByTestId("engine-status")).toContainText("ready, open a .blend file", { timeout: 20_000 }); const app = page.locator("main.blender-app"); await page.getByTestId("blend-file-input").setInputFiles(materialBlend); await expect(page.getByText("AttributeMeshObject", { exact: true })).toBeVisible(); await page.getByTestId("blend-file-input").setInputFiles({ name: "invalid.blend", mimeType: "application/octet-stream", buffer: Buffer.from([0x42, 0x41, 0x44]), }); await expect(page.getByTestId("engine-status")).toHaveText("Engine: .blend open failed"); await expect(app).toHaveAttribute("data-last-diagnostic-code", "BLEND_OPEN_FAILED"); await page.locator("label.file-button input[type=file]").setInputFiles({ name: "invalid.png", mimeType: "image/png", buffer: Buffer.from("not-a-png"), }); await expect(page.getByTestId("engine-status")).toHaveText("Image import failed"); await expect(app).toHaveAttribute("data-last-diagnostic-code", "IMAGE_IMPORT_FAILED"); await expect(app).toHaveAttribute("data-diagnostic-count", "2"); const downloadPromise = page.waitForEvent("download"); await page.getByTestId("export-diagnostics").click(); const reportPath = await (await downloadPromise).path(); const report = JSON.parse(await fs.readFile(reportPath!, "utf8")); expect(report.entries.map((entry: { code: string }) => entry.code)).toEqual(["BLEND_OPEN_FAILED", "IMAGE_IMPORT_FAILED"]); expect(report.entries.every((entry: { detail: string }) => entry.detail.length > 0)).toBe(true); expect(report.entries[0].summary).toBe("Engine: .blend open failed"); expect(report.entries[1].summary).toBe("Image import failed"); });