import fs from 'node:fs'; import path from 'node:path'; import assert from 'node:assert/strict'; import {chromium,expect} from '@playwright/test'; import {login,call} from './role-permissions-live.mjs'; const report=path.resolve('reports/animation-capabilities-20260912'),f=JSON.parse(fs.readFileSync(path.join(report,'fixture.json'),'utf8')); const session=await login('root'),browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']}); const context=await browser.newContext({viewport:{width:1920,height:1080}}); await context.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);sessionStorage.setItem('unreal-tran:web:refresh-token:v1',s.refreshToken)},session); const page=await context.newPage(),checks=[],errors=[];page.on('pageerror',e=>errors.push(e.message)); try{ await page.goto(`http://127.0.0.1:6180/content/training-projects/${f.animationTrainingId}/legacy`); const open=page.locator('[data-action="open-content-resources"][data-resource-scope="animation"]'); await expect(open).toBeVisible({timeout:60000});await open.click(); const key=`animation-content-model-${f.modelId}-${f.modelVersionId}-work`; const button=page.locator(`[data-action="bind-content-resource"][data-resource-id="${key}"]`); await expect(button).toBeVisible({timeout:60000}); await expect(page.locator(`[data-resource-id="animation-content-model-${f.modelId}-${f.modelVersionId}-reset"]`)).toBeVisible(); await button.scrollIntoViewIfNeeded();await page.screenshot({path:path.join(report,'11-training-action-library.png')}); if(await button.innerText()==='取消绑定')await button.click(); await button.click(); await page.locator('[data-action="close-dialog"]').last().click(); await expect(page.locator('.te-step-animation-binding')).toContainText('旋出与清洁演示'); const save=page.locator('[data-action="save"]');console.log('SAVE_COUNT',await save.count()); const wait=page.waitForResponse(r=>r.request().method()==='PUT'&&r.url().includes(`/content/projects/${f.animationTrainingId}`),{timeout:60000}); await save.click();const response=await wait;assert.ok(response.ok(),(await response.text()).slice(0,200)); const result=await call(`tran/v1/content/projects/${f.animationTrainingId}`,session.accessToken); const binding=result.data.currentVersion.content.steps[0].animationBinding; assert.equal(binding.clipName,'旋出与清洁演示');assert.equal(binding.modelAssetId,`content-model-${f.modelId}-${f.modelVersionId}`); checks.push({name:'训练编辑器从正式模型库选择命名动作并通过真实保存接口持久化',pass:true,binding}); await page.screenshot({path:path.join(report,'12-training-action-bound.png')}); const assignment=await call(`tran/v1/teaching/assignments/${f.animationAssignmentId}`,session.accessToken); assert.equal(assignment.data.contentVersionId,f.animationAssignmentContentVersionId || '482'); checks.push({name:'训练编辑草稿不会覆盖已创建任务的固定发布快照',pass:true}); assert.equal(errors.length,0,errors.join('\n')); }catch(error){checks.push({name:'训练动画绑定端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await page.screenshot({path:path.join(report,'training-editor-failure.png')}).catch(()=>{});} finally{fs.writeFileSync(path.join(report,'training-editor-checks.json'),JSON.stringify({checks,errors},null,2));await browser.close();}