browser-check.cjs 4.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344
  1. const {chromium}=require('playwright');const fs=require('fs');
  2. (async()=>{const browser=await chromium.launch({channel:'chrome',headless:true}),page=await browser.newPage(),errors=[];page.on('pageerror',e=>errors.push(e.message));page.on('dialog',d=>d.accept());
  3. await page.addInitScript(()=>{
  4. if(location.pathname.includes('/stu/')) {
  5. localStorage.setItem('xiaoshu.angular.session',JSON.stringify({token:'r:fixture',user:{userId:100,groupId:1,honeyName:'隔离学员'}}));
  6. const prefix='xiaoshu.learning.100.100.practice.',words=[{GeneralID:1,Title:'apple',sy:'苹果'},{GeneralID:2,Title:'banana',sy:'香蕉'},{GeneralID:3,Title:'orange',sy:'橙子'},{GeneralID:4,Title:'pear',sy:'梨'}];
  7. for(const [key,value]of Object.entries({learn_mode:'newword',choose_newword_learn:words,_page_word_orilist:[words],_page_current_learn:1}))localStorage.setItem(prefix+key,JSON.stringify(value));
  8. return;
  9. }
  10. localStorage.setItem('xiaoshu.angular.session',JSON.stringify({token:'r:fixture',user:{userId:30,groupId:3,honeyName:'测试老师'},student:{userId:20,groupId:1,honeyName:'测试学员'}}));
  11. });
  12. let learned=52,fail=true;const order={GeneralID:901,szyh:20,pl:30,dszt:10,HoneyName:'测试学员',bindingId:'binding',kcid:161,allowedActions:['continue','complete'],updatedAt:new Date().toISOString()};
  13. await page.route('**/*',r=>['127.0.0.1','localhost'].includes(new URL(r.request().url()).hostname)?r.continue():r.abort());
  14. await page.route('**/cloud-functions/**',async r=>{const p=r.request().postDataJSON()?.params||{};let result=[];
  15. if(p.action==='e_order_detail')result=[order];
  16. if(p.action==='app_lesson_study')result={record:null,batches:[],pending:0};
  17. if(p.action==='app_lesson_courses')result={items:[{bindingId:'binding',kcid:161,Title:'高中词库',categoryKey:'word',syjd:learned===100?'{"172":"172@必修一全册@1"}':'{}'}]};
  18. if(p.action==='node_list')result=[{NodeID:172,NodeName:'必修一全册',total:100,yx_word:learned}];
  19. if(p.action==='app_live_versions')result={version:'fixture',scopeVersions:{},syncStatus:{state:'current'}};
  20. if(p.action==='app_lesson_unit_complete'){
  21. if(fail){fail=false;return r.fulfill({status:503,json:{retcode:-1,retmsg:'保存失败'}});}
  22. learned=100;result={nodeId:172,total:100,learned:100,percent:100,syjd:'{"172":"172@必修一全册@1"}'};
  23. }
  24. if(p.action==='content_list')result=Array.from({length:20},(_,i)=>({GeneralID:300+i,Title:'extra'+i,sy:'拓展释义'+i}));
  25. await r.fulfill({json:{retcode:0,result}});
  26. });
  27. const out='docs/qa/learning-experience-2026-10-13';
  28. await page.setViewportSize({width:375,height:812});await page.goto('http://127.0.0.1:4200/pages/shop/get_order?id=901');
  29. await page.getByText('已学 52%',{exact:true}).waitFor();await page.getByRole('button',{name:'标记已学完'}).click();
  30. await page.getByRole('button',{name:'标记已学完'}).waitFor();await page.waitForFunction(()=>!document.querySelector('.mark-complete')?.disabled);
  31. if(!(await page.getByText('已学 52%',{exact:true}).count()))throw Error('failed save changed progress');
  32. await page.getByRole('button',{name:'标记已学完'}).click();await page.getByText('已学 100%',{exact:true}).waitFor();await page.reload();await page.getByText('已学 100%',{exact:true}).waitFor();
  33. for(const width of [375,812]){await page.setViewportSize({width,height:812});await page.locator('.unit-grid').scrollIntoViewIfNeeded();await page.screenshot({path:out+'/unit-'+width+'.png'});if(await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth))throw Error('overflow');}
  34. await page.setViewportSize({width:375,height:812});await page.goto('http://127.0.0.1:4200/pages/stu/stu4');
  35. await page.locator('.option-list button').first().waitFor();
  36. const options=await page.locator('.option-list button').allTextContents();
  37. if(options.length!==4||options.filter(x=>x.startsWith('拓展释义')).length<2)throw Error('missing external choices');
  38. await page.screenshot({path:out+'/listening-375.png',fullPage:true});
  39. const audio=await page.evaluate(async()=>{const results=[];for(const kind of ['success','error']){const response=await fetch('/assets/audio/learning-'+kind+'.wav');const data=await response.arrayBuffer();const context=new AudioContext();const buffer=await context.decodeAudioData(data);results.push({kind,ok:response.ok,duration:buffer.duration});await context.close();}return results;});
  40. if(audio.some(a=>!a.ok||a.duration<.1||a.duration>.5))throw Error('invalid audio asset');
  41. await browser.close();
  42. fs.writeFileSync(out+'/browser-result.json',JSON.stringify({viewports:[375,812],failedSavePreserved:true,savedProgress:100,refreshProgress:100,listeningOptions:options,audio,pageErrors:errors},null,2));
  43. if(errors.length)throw Error(errors.join('\n'));console.log('Unit progress failure, success, reload and responsive browser checks passed');
  44. })().catch(e=>{console.error(e);process.exit(1)});