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 BASIC_SCENE = path.resolve(HERE, "../../../tests/files/web/basic_scene.blend"); test.describe("Outliner React treeview production path", () => { test("renders native rows with tree semantics and keeps selection wired", async ({ page }) => { await page.goto("/?ui=web"); await page.locator('[data-testid="blend-file-input"]').setInputFiles(BASIC_SCENE); 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(); const tree = page.locator(".outliner-tree").first(); await expect(tree).toBeVisible({ timeout: 20_000 }); await expect(page.getByRole("tree").first()).toBeVisible(); const items = page.getByRole("treeitem"); await expect.poll(async () => items.count(), { timeout: 20_000 }).toBeGreaterThan(0); const firstItem = items.first(); await expect(firstItem).toHaveAttribute("aria-level", /^\d+$/); await expect(firstItem).toHaveAttribute("aria-label", /.+/); const name = firstItem.locator(".tree-node-name").first(); if (await name.count()) { await name.click(); await expect.poll(async () => page.locator('[role="treeitem"][aria-selected="true"]').count(), { timeout: 15_000 }).toBeGreaterThan(0); } const expand = page.locator(".tree-expand").first(); if (await expand.count()) { const before = await items.count(); const label = await expand.getAttribute("aria-label"); if (label?.startsWith("展开")) { await expand.click(); await expect.poll(async () => items.count(), { timeout: 15_000 }).toBeGreaterThan(before); } } }); });