Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

57 linhas
5.5 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. const fixture=JSON.parse(fs.readFileSync(path.join(work,'fixture.json'))),checks=[],errors=[]
  7. const session=await login('root'),token=session.accessToken
  8. const check=(name,pass,detail='')=>{checks.push({name,pass,detail});console.log(pass?'PASS':'FAIL',name);if(!pass)process.exitCode=1}
  9. const base=`tran/v1/teaching/guides/${fixture.id}`
  10. const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']})
  11. const context=await browser.newContext({viewport:{width:1920,height:1080}})
  12. 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)
  13. const page=await context.newPage();page.on('pageerror',e=>errors.push(e.message))
  14. 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})})
  15. page.on('console',m=>{if(m.text().startsWith('NOTICE'))console.log(m.text())})
  16. page.on('response',r=>{if(r.status()>=400)console.log('HTTP',r.status(),new URL(r.url()).pathname)})
  17. const shot=async name=>page.screenshot({path:path.join(report,name+'.png'),fullPage:false})
  18. try{
  19. const assets=await call(`${base}/deliveries`,token);check('发布版本提供两份正式交付物',assets.data?.length===2)
  20. for(const format of (process.argv.includes('--browser-only')?[]:['OFD','OFFLINE_HTML'])){const r=await context.request.get(`http://127.0.0.1:6180/api/${base}/delivery?format=${format}`,{headers:{Authorization:`Bearer ${token}`},timeout:180000});check('统一文件服务交付物下载 '+format,r.ok());if(r.ok())fs.writeFileSync(path.join(report,format==='OFD'?'formal.ofd':'formal-offline.zip'),await r.body())}
  21. const before=(await call(`${base}/reader-state`,token)).data
  22. const command={...before,pageId:'page:1',pageIndex:1,pageCount:3,favorite:true,notes:[{id:'formal-note',pageId:'page:1',text:'正式版本笔记:核对狐狸模型与卡车资源,注意安全检查。'}]}
  23. const saved=await call(`${base}/reader-state`,token,command,'PUT');check('阅读位置、收藏、笔记真实写入',saved.status===200&&saved.data.favorite&&saved.data.notes.length===1)
  24. const stale=await call(`${base}/reader-state`,token,command,'PUT');check('旧乐观锁版本被拒绝',stale.status===409)
  25. const release=await call(`${base}/reader-state`,token,{...command,versionId:'1'},'PUT');check('旧发布版本不能覆盖当前阅读记录',release.status===409)
  26. const stats=await call(`${base}/reader-statistics`,token);check('收藏和笔记进入真实统计',stats.data?.favorites>=1&&stats.data?.notes>=1)
  27. await page.goto('http://127.0.0.1:6180/teaching/guides')
  28. await page.getByPlaceholder('搜索指导书名称或编码').fill('正式闭环-狐狸与卡车指导书-20260910')
  29. await page.getByPlaceholder('搜索指导书名称或编码').press('Enter')
  30. await expect(page.locator('.guide-card-v2')).toHaveCount(1)
  31. await page.locator('.guide-card-v2 button').first().click()
  32. await expect(page.locator('.legacy-published-reader .ofd-app')).toBeVisible({timeout:60000})
  33. await expect(page.getByRole('button',{name:'取消收藏',exact:true})).toBeVisible()
  34. await page.getByRole('button',{name:/笔记 1/}).click()
  35. await expect(page.locator('.reader-note')).toContainText('正式版本笔记')
  36. await shot('05-reader-notes')
  37. await page.getByPlaceholder('记录当前页的阅读笔记').fill('浏览器实测新增笔记:页码和内容由正式服务保存。')
  38. const noteResponse=page.waitForResponse(r=>r.url().endsWith('/reader-state')&&r.request().method()==='PUT')
  39. await page.getByRole('button',{name:'添加笔记',exact:true}).click()
  40. check('浏览器笔记保存请求成功',(await noteResponse).ok())
  41. await page.locator('.el-drawer__close-btn:visible').click()
  42. await page.locator('.legacy-published-reader [data-action="preview-next"]').click()
  43. await page.waitForTimeout(1300);await shot('06-reader-layout')
  44. const persisted=await call(`${base}/reader-state`,token);check('浏览器翻页位置已同步',persisted.data?.pageIndex===2)
  45. await page.locator('.legacy-published-reader [data-action="preview-tab"][data-tab="interactive"]').click()
  46. await page.waitForTimeout(3500);await shot('07-reader-interactive')
  47. await page.locator('.legacy-published-reader canvas').first().scrollIntoViewIfNeeded()
  48. await page.waitForTimeout(1200);await shot('09-reader-model')
  49. check('教学动态阅读显示三维画布',await page.locator('.legacy-published-reader canvas').count()>0)
  50. await page.goto('http://127.0.0.1:6180/content/ofd/api')
  51. await page.getByRole('button',{name:'执行调用',exact:true}).click()
  52. await expect(page.locator('.guide-http-page pre')).toContainText('records')
  53. await shot('08-http-integration')
  54. check('对外接口面板返回真实工程数据',await page.locator('.guide-http-page pre').textContent().then(t=>t.includes('GUIDE-')))
  55. }catch(e){check('浏览器闭环流程',false,e.message);await shot('failure-review')}
  56. finally{check('浏览器无脚本异常',errors.length===0,errors.join('\n'));fs.writeFileSync(path.join(report,'review.json'),JSON.stringify({checks,errors},null,2));await browser.close()}