import { test, expect } from '@playwright/test'; import fs from 'node:fs'; import path from 'node:path'; const fixture = path.resolve(import.meta.dirname, '../../../tests/files/web/basic_scene.blend'); async function boot(page: import('@playwright/test').Page) { await page.goto('/'); await page.getByTestId('blend-file-input').setInputFiles(fixture); await expect(page.getByTestId('engine-status')).toContainText(/SceneIR/, { timeout: 60_000 }); await page.getByTestId('native-web-tools-toggle').click({ force: true }); await page.waitForTimeout(1500); } test('console assignment then Ctrl+Z in Layout', async ({ page }, info) => { const record: Record = {}; await boot(page); await page.evaluate(() => { const b = Array.from(document.querySelectorAll('nav[aria-label="工作区"] button')) .find(x => (x.textContent ?? '').trim() === 'Scripting') as HTMLButtonElement | undefined; b?.click(); }); await expect(page.getByLabel('Python 输入', { exact: true }).last()).toBeVisible({ timeout: 30_000 }); const input = page.getByLabel('Python 输入', { exact: true }).last(); const before = await page.evaluate(() => document.querySelector('[data-testid="engine-status"]')?.textContent ?? ''); record.statusBeforeAssign = before; await input.fill("bpy.data.objects['BasicCube'].location.x = 3"); await input.press('Enter'); await page.waitForTimeout(4000); record.consoleText = await page.getByRole('region', { name: 'Python Console', exact: true }).locator('pre').first().textContent().catch(() => null); await page.evaluate(() => { const b = Array.from(document.querySelectorAll('nav[aria-label="工作区"] button')) .find(x => (x.textContent ?? '').trim() === 'Layout') as HTMLButtonElement | undefined; b?.click(); }); await page.waitForTimeout(2500); record.workspaceAfterSwitch = await page.locator('main').getAttribute('data-workspace'); record.statusAfterAssign = await page.getByTestId('engine-status').textContent().catch(() => null); await page.keyboard.press('Control+z'); await page.waitForTimeout(3000); record.statusAfterUndo = await page.getByTestId('engine-status').textContent().catch(() => null); record.historyText = await page.evaluate(() => document.body.innerText.slice(-500)); fs.writeFileSync(info.outputPath('console-undo.json'), JSON.stringify(record, null, 1)); console.log(JSON.stringify(record, null, 1)); }); test('native modal cancel leaves mesh unchanged', async ({ page }, info) => { const record: Record = {}; await page.goto('/'); await page.getByTestId('blend-file-input').setInputFiles(fixture); await expect(page.getByTestId('engine-status')).toContainText(/SceneIR/, { timeout: 60_000 }); record.desktopVisible = await page.locator('section[aria-label="Blender 桌面"]').isVisible().catch(() => null); const canvas = page.locator('section[aria-label="Blender 桌面"] canvas').first(); record.canvasCount = await page.locator('section[aria-label="Blender 桌面"] canvas').count(); if (record.canvasCount) { await canvas.click({ position: { x: 400, y: 300 }, force: true }).catch(() => {}); } await page.keyboard.press('g'); await page.waitForTimeout(600); record.statusDuringModal = await page.getByTestId('engine-status').textContent().catch(() => null); record.bodyDuringModal = await page.evaluate(() => document.body.innerText.slice(-400)); await page.keyboard.press('Escape'); await page.waitForTimeout(2000); record.statusAfterEscape = await page.getByTestId('engine-status').textContent().catch(() => null); record.bodyAfterEscape = await page.evaluate(() => document.body.innerText.slice(-400)); record.alerts = await page.locator('[role="alert"]').allTextContents(); fs.writeFileSync(info.outputPath('modal-cancel.json'), JSON.stringify(record, null, 1)); console.log(JSON.stringify(record, null, 1)); });