import fs from 'node:fs' import {chromium,expect} from '@playwright/test' import {login} from './role-permissions-live.mjs' const session=await login('root'),checks=[] const browser=await chromium.launch({channel:'msedge',headless:true}) const page=await browser.newPage({viewport:{width:1920,height:1080}}) await page.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);sessionStorage.setItem('unreal-tran:web:refresh-token:v1',s.refreshToken)},session) let id try{ await page.goto('http://127.0.0.1:6180/content/ofd') await expect(page.getByRole('button',{name:'新建指导书',exact:true})).toBeVisible() const result=process.env.GUIDE_MEDIA_ID ? await page.evaluate(async id=>{ const {FormalGuideStore}=await import('/src/features/guide-legacy/formalStore.ts') const store=new FormalGuideStore();await store.load(id) const step=store.record.state.chapters[0].steps[0] const valid=step.mediaBlocks.every(b=>b.kind==='video'?!!b.poster:!!b.imageSource)&&!JSON.stringify(step).includes('blob:') store.dispose();return {id,valid} },process.env.GUIDE_MEDIA_ID) : await page.evaluate(async()=>{ const {FormalGuideStore}=await import('/src/features/guide-legacy/formalStore.ts') const store=new FormalGuideStore();await store.createOFDProject({title:'正式图片视频资源回归-20260911'}) const canvas=document.createElement('canvas');canvas.width=400;canvas.height=240 const ctx=canvas.getContext('2d');ctx.fillStyle='#168a72';ctx.fillRect(0,0,400,240);ctx.fillStyle='white';ctx.font='24px sans-serif';ctx.fillText('Formal image resource',45,130) const png=await new Promise(resolve=>canvas.toBlob(resolve,'image/png')) const image=await store.assetStore.put(png,{name:'guide-image.png'}) const video=await store.assetStore.put(await(await fetch('/videos/physical-training-real-main-720p.mp4')).blob(),{name:'guide-video.mp4'}) const state=structuredClone(store.record.state) state.resources=[{...image,id:image.assetKey,name:'guide-image.png',type:'图片',category:'image',resourceUrl:`asset://${image.assetKey}`,blob:undefined},{...video,id:video.assetKey,name:'guide-video.mp4',type:'视频',category:'video',resourceUrl:`asset://${video.assetKey}`,blob:undefined}] const step=state.chapters[0].steps[0];step.image=`asset://${image.assetKey}` step.mediaBlocks=[{id:'image-block',kind:'image',imageSource:`asset://${image.assetKey}`,x:120,y:62,width:72,height:58},{id:'video-block',kind:'video',assetId:video.assetKey,assetName:'guide-video.mp4',poster:canvas.toDataURL('image/png'),x:120,y:130,width:72,height:58}] await store.saveOFDProjectState(store.project.id,state) const id=store.project.id;store.dispose();const loaded=new FormalGuideStore();await loaded.load(id) const saved=loaded.record.state.chapters[0].steps[0] const valid=saved.mediaBlocks.every(b=>b.kind==='video'?!!b.poster:!!b.imageSource)&&!JSON.stringify(saved).includes('blob:') loaded.dispose();return {id,valid} }) id=result.id;checks.push({name:'图片与视频上传、截图引用保存后重开保持',pass:result.valid}) await page.goto(`http://127.0.0.1:6180/content/ofd/${id}/studio?view=preview`) await expect(page.locator('.ofd-preview-pager')).toBeVisible({timeout:60000}) checks.push({name:'OFD 预览读取受控图片和截图',pass:!(await page.locator('.ofd-analysis-empty').allTextContents()).some(t=>t.includes('失败'))}) await page.locator('[data-action="preview-tab"][data-tab="interactive"]').click() const video=page.locator('.ofd-ia-media-item.video video') await expect(video).toHaveAttribute('src',/^blob:/,{timeout:30000}) await expect.poll(()=>video.evaluate(v=>v.readyState)).toBeGreaterThanOrEqual(2) await video.evaluate(v=>v.play());await expect.poll(()=>video.evaluate(v=>v.currentTime)).toBeGreaterThan(0) await video.scrollIntoViewIfNeeded();await page.screenshot({path:'reports/guide-formal-20260910/18-formal-video.png'}) checks.push({name:'正式视频通过受控字节恢复并实际播放',pass:true}) }catch(e){checks.push({name:'图片视频资源闭环',pass:false,detail:e.message});process.exitCode=1;await page.screenshot({path:'reports/guide-formal-20260910/media-failure.png'})} finally{console.log(checks);fs.writeFileSync('reports/guide-formal-20260910/media.json',JSON.stringify({projectId:id,checks},null,2));await browser.close()}