You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

78 regels
6.1 KiB

  1. import fs from 'node:fs'
  2. import path from 'node:path'
  3. import { chromium, expect } from '@playwright/test'
  4. import { login, call } from './role-permissions-live.mjs'
  5. const report=path.resolve('reports/guide-formal-20260910'),work=path.resolve('../../.codex-tmp/guide-formal')
  6. fs.mkdirSync(report,{recursive:true});fs.mkdirSync(work,{recursive:true})
  7. const phase=process.argv[2]||'create', fixturePath=path.join(work,'fixture.json')
  8. const fixture=fs.existsSync(fixturePath)?JSON.parse(fs.readFileSync(fixturePath)):{}
  9. const session=await login('root'),browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']})
  10. const context=await browser.newContext({viewport:{width:1920,height:1080}})
  11. 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)
  12. const page=await context.newPage(),checks=[],errors=[]
  13. page.on('pageerror',e=>errors.push(e.message))
  14. page.on('response',r=>{if(r.status()>=400) console.log('HTTP',r.status(),new URL(r.url()).pathname)})
  15. await page.addInitScript(()=>{new MutationObserver(()=>{for(const el of document.querySelectorAll('.ofd-toast,.el-message'))if(!el.dataset.reported){el.dataset.reported='true';console.log('NOTICE',el.textContent)}}).observe(document,{childList:true,subtree:true})})
  16. page.on('console',m=>{if(m.text().startsWith('NOTICE'))console.log(m.text())})
  17. const shot=async name=>page.screenshot({path:path.join(report,name+'.png')})
  18. const check=(name,pass,detail='')=>{checks.push({name,pass,detail});console.log(pass?'PASS':'FAIL',name);if(!pass)process.exitCode=1}
  19. try{
  20. if(phase==='create'){
  21. await page.goto('http://127.0.0.1:6180/content/ofd')
  22. await page.getByRole('button',{name:'新建指导书',exact:true}).click()
  23. await page.locator('[data-wizard="name"]').fill('正式闭环-狐狸与卡车指导书-20260910')
  24. await page.locator('[data-wizard="equipment"]').fill('狐狸与牛奶卡车功能测试素材')
  25. await page.locator('[data-wizard="owner"]').fill('功能闭环测试')
  26. await page.locator('[data-action="wizard-next"]').click()
  27. await page.locator('[data-action="wizard-template"][data-template="__blank__"]').click()
  28. await page.locator('[data-action="wizard-next"]').click()
  29. const response=page.waitForResponse(r=>r.url().endsWith('/content/projects')&&r.request().method()==='POST')
  30. await page.locator('[data-action="wizard-next"]').click()
  31. const created=await response;check('新建请求实际进入内容服务',created.status()===200,await created.text())
  32. await page.waitForURL(/\/ofd\/\d+\/studio/)
  33. fixture.id=page.url().match(/\/ofd\/(\d+)\/studio/)[1];fs.writeFileSync(fixturePath,JSON.stringify(fixture))
  34. const server=await call(`tran/v1/content/projects/${fixture.id}`,session.accessToken)
  35. check('正式后台可查询新工程',server.status===200)
  36. await shot('01-formal-created')
  37. }
  38. if(phase==='publish'){
  39. await page.goto(`http://127.0.0.1:6180/content/ofd/${fixture.id}/studio?view=editor`)
  40. await expect(page.locator('.ofd-view-editor')).toBeVisible({timeout:60000})
  41. await page.locator('[data-action="save"]').click();await page.waitForTimeout(700)
  42. await page.locator('[data-action="open-publish"]').click()
  43. const response=page.waitForResponse(r=>r.url().endsWith('/publish')&&r.request().method()==='POST',{timeout:600000})
  44. await page.locator('[data-action="lifecycle-action"][data-lifecycle-action="publish"]').click()
  45. const result=await response;check('正式发布事务完成',result.ok(),(await result.text()).slice(0,400))
  46. await page.waitForTimeout(700);await shot('04-formal-published')
  47. const guides=await call('tran/v1/teaching/guides?keyword='+encodeURIComponent('正式闭环-狐狸与卡车指导书-20260910'),session.accessToken)
  48. check('教学指导书列表出现正式发布工程',guides.data?.records?.some(r=>r.projectId===fixture.id))
  49. const assets=await call(`tran/v1/teaching/guides/${fixture.id}/deliveries`,session.accessToken)
  50. check('发布版本提供 OFD 与离线 ZIP',assets.data?.length===2)
  51. for(const format of ['OFD','OFFLINE_HTML']){
  52. const result=await context.request.get(`http://127.0.0.1:6180/api/tran/v1/teaching/guides/${fixture.id}/delivery?format=${format}`,{headers:{Authorization:`Bearer ${session.accessToken}`},timeout:180000})
  53. check('下载正式交付物 '+format,result.ok());if(result.ok())fs.writeFileSync(path.join(report,format==='OFD'?'formal.ofd':'formal-offline.zip'),await result.body())
  54. }
  55. }
  56. if(phase==='upload'){
  57. await page.goto(`http://127.0.0.1:6180/content/ofd/${fixture.id}/studio?view=editor`)
  58. await expect(page.locator('.ofd-view-editor')).toBeVisible({timeout:60000})
  59. for(const name of ['09-Fox.glb','08-CesiumMilkTruck.glb']){
  60. const wait=page.waitForResponse(r=>r.url().includes('/assets/upload')&&r.request().method()==='POST')
  61. await page.locator('#ofd-resource-file').setInputFiles(path.resolve('../../文档/素材',name))
  62. const response=await wait;check('上传 '+name,response.ok(),(await response.text()).slice(0,300))
  63. await page.waitForTimeout(1800)
  64. }
  65. await page.locator('[data-action="save"]').click();await page.waitForTimeout(1200)
  66. await shot('02-formal-assets')
  67. await page.reload();await expect(page.locator('.ofd-view-editor')).toBeVisible({timeout:60000})
  68. await page.locator('[data-action="select-step"]').first().click()
  69. await page.locator('[data-action="right-dock-tab"]').filter({hasText:'资源'}).click()
  70. await page.locator('[data-action="library-insert"][data-kind="model"]').filter({hasText:'Fox'}).first().click()
  71. await page.locator('[data-action="media-open"]').first().click()
  72. await expect(page.locator('.ofd-media-panel canvas')).toBeVisible({timeout:30000})
  73. await page.waitForTimeout(1200);await shot('03-formal-fox')
  74. check('刷新后受控模型资源恢复显示',true)
  75. }
  76. }catch(e){check(phase,false,e.message);console.log(await page.locator('.legacy-guide-page').evaluate(el=>el.__vueParentComponent?.setupState?.mountError).catch(()=>''));await shot(phase+'-failure').catch(()=>{})}
  77. finally{fs.writeFileSync(path.join(report,phase+'.json'),JSON.stringify({checks,errors},null,2));await browser.close()}