No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 

43 líneas
3.6 KiB

  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import http from 'node:http';
  4. import {execFileSync} from 'node:child_process';
  5. import assert from 'node:assert/strict';
  6. import {chromium,expect} from '@playwright/test';
  7. const report=path.resolve('reports/animation-capabilities-20260912'),root=path.join(report,'offline');
  8. execFileSync('python',['-c',`import zipfile,pathlib
  9. root=pathlib.Path(${JSON.stringify(root)}).resolve()
  10. with zipfile.ZipFile(${JSON.stringify(path.join(report,'animation-guide.zip'))}) as archive:
  11. for entry in archive.infolist():
  12. target=(root/entry.filename).resolve()
  13. if not target.is_relative_to(root): raise ValueError('Invalid archive path')
  14. archive.extract(entry,root)
  15. `]);
  16. const mime={'.html':'text/html','.js':'text/javascript','.wasm':'application/wasm','.glb':'model/gltf-binary','.css':'text/css','.png':'image/png','.jpg':'image/jpeg'};
  17. const server=http.createServer((req,res)=>{const file=path.resolve(root,'.'+decodeURIComponent(new URL(req.url,'http://localhost').pathname).replace(/\/$/,'/index.html'));if(!file.startsWith(root+path.sep)||!fs.existsSync(file)){res.writeHead(404).end();return;}res.setHeader('Content-Type',mime[path.extname(file)]||'application/octet-stream');fs.createReadStream(file).pipe(res);});
  18. await new Promise(resolve=>server.listen(18779,'127.0.0.1',resolve));
  19. const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
  20. const page=await browser.newPage({viewport:{width:1440,height:1000}}),checks=[],errors=[],external=[];
  21. page.on('pageerror',e=>errors.push(e.message));
  22. await page.route('**/*',route=>{const url=new URL(route.request().url());if(url.origin!=='http://127.0.0.1:18779'&&!['data:','blob:'].includes(url.protocol)){external.push(url.origin);return route.abort();}return route.continue();});
  23. try{
  24. await page.goto('http://127.0.0.1:18779/');
  25. await page.locator('[data-jump="1"]').first().click();
  26. await page.locator('[data-action="open-model"]').first().click();
  27. await expect(page.locator('[data-model-status]')).not.toContainText(/正在加载|失败|未加载/,{timeout:60000});
  28. await expect(page.locator('[data-model-stage] canvas')).toBeVisible({timeout:60000});
  29. await page.locator('[data-model-stage] canvas').scrollIntoViewIfNeeded();
  30. const canvas=page.locator('[data-model-stage] canvas');
  31. const before=await canvas.screenshot();await page.screenshot({path:path.join(report,'07-offline-before.png')});
  32. await page.locator('[data-model-action="play-animation"]').click();
  33. await expect(page.locator('[data-model-status]')).toContainText('旋出与清洁演示');
  34. await page.waitForTimeout(1800);const after=await canvas.screenshot();
  35. assert.notDeepEqual(before,after,'Offline canvas must change while the authored action plays');
  36. await page.locator('[data-model-action="pause-animation"]').click();
  37. await page.screenshot({path:path.join(report,'08-offline-animation.png')});
  38. checks.push({name:'下载的离线包可播放命名组合动作,画布发生变化并可暂停',pass:true});
  39. assert.equal(external.length,0);assert.equal(errors.length,0,errors.join('\n'));
  40. checks.push({name:'断开业务服务与公网后仍可加载模型、引擎和动画',pass:true});
  41. }catch(error){checks.push({name:'离线动画端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await page.screenshot({path:path.join(report,'offline-failure.png')}).catch(()=>{});}
  42. finally{fs.writeFileSync(path.join(report,'offline-checks.json'),JSON.stringify({checks,errors,external},null,2));await browser.close();await new Promise(resolve=>server.close(resolve));}