check-exam-history-browser.mjs 6.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. #!/usr/bin/env node
  2. // Isolated local fixture browser acceptance. Does not read or write online accounts.
  3. import {chromium} from 'playwright';
  4. import {createServer} from 'node:http';
  5. import {readFileSync,existsSync,mkdirSync,writeFileSync} from 'node:fs';
  6. import {resolve,extname} from 'node:path';
  7. import assert from 'node:assert/strict';
  8. const root=resolve('dist/xiaoshu-mobile/browser'),out=resolve('tmp/exam-history-qa');mkdirSync(out,{recursive:true});
  9. const server=createServer((req,res)=>{let file=resolve(root,'.'+decodeURIComponent(new URL(req.url,'http://local').pathname));if(!file.startsWith(root+'/'))file=root+'/index.html';if(!existsSync(file)||!extname(file))file=root+'/index.html';try{res.setHeader('Content-Type',({'.html':'text/html','.js':'text/javascript','.css':'text/css','.png':'image/png','.svg':'image/svg+xml','.woff2':'font/woff2'})[extname(file)]||'application/octet-stream');res.end(readFileSync(file));}catch{res.statusCode=404;res.end();}});
  10. await new Promise(r=>server.listen(0,'127.0.0.1',r));const origin='http://127.0.0.1:'+server.address().port;
  11. const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
  12. const calls=[],errors=[],records=Array.from({length:12},(_,i)=>({GeneralID:'old-'+i,testedAt:new Date(Date.UTC(2026,9,14-i,8)).toISOString(),askid:i%2+1,resultAvailable:true,df:8+i,totalScore:48,dontKnow:40-i,wrong:0}));
  13. const baseline=JSON.stringify(records);let failSummary=true,failHistory=false,answerId=0;
  14. try{
  15. const context=await browser.newContext({viewport:{width:390,height:844}});
  16. await context.addInitScript(()=>localStorage.setItem('xiaoshu.angular.session',JSON.stringify({token:'r:exam-fixture-only',user:{userId:100,groupId:1,honeyName:'测试学生'}})));
  17. await context.route('**/*',async route=>{
  18. const u=new URL(route.request().url());if(u.origin!==origin)return route.abort();if(!u.pathname.startsWith('/cloud-functions/'))return route.continue();
  19. const p=route.request().postDataJSON().params;calls.push(p);let result=[];
  20. if(p.action==='app_exam_history'){if(failHistory)return route.fulfill({status:500,json:{retcode:-1,retmsg:'模拟历史读取失败'}});const start=(p.page-1)*10;result={items:records.slice(start,start+10),hasMore:records.length>start+10};}
  21. if(p.action==='vote_ask')result=[{ID:Number(p.id),Title:'第'+p.id+'组问题'}];
  22. if(p.action==='vote_question')result=[1,2].map(i=>({ID:i,AskID:Number(p.askid),QTitle:'apple',QContent:'苹果',QType:1,QOption:[{text:'苹果',value:'A'},{text:'香蕉',value:'B'}]}));
  23. if(p.action==='vote_add')result=++answerId;
  24. if(p.action==='content_update')throw Error('Must never overwrite a previous result');
  25. if(p.action==='content_add'){
  26. assert.equal(JSON.parse(p.content).ModelID,61);assert.equal(JSON.parse(p.content).GeneralID,undefined);
  27. if(failSummary)return route.fulfill({status:409,json:{retcode:-1,retmsg:'模拟摘要保存失败'}});
  28. const row=JSON.parse(p.addon);records.unshift({...row,GeneralID:'new-'+answerId,testedAt:new Date().toISOString(),resultAvailable:true});result='new-'+answerId;
  29. }
  30. await route.fulfill({json:{retcode:1,result}});
  31. });
  32. const page=await context.newPage();page.on('pageerror',e=>errors.push(e.message));
  33. await page.goto(origin+'/pages/his/exam');await page.locator('.exam-history-row').first().waitFor();assert.equal(await page.locator('.exam-history-row').count(),10);assert.equal(await page.locator('.exam-comparison article').count(),2);
  34. await page.getByRole('button',{name:'查看更多记录',exact:true}).click();await page.waitForFunction(()=>document.querySelectorAll('.exam-history-row').length===12);
  35. await page.locator('.exam-history-row').nth(11).click();await page.waitForFunction(()=>document.querySelector('.exam-comparison')?.textContent.includes('19 / 48'));assert.match(await page.locator('.exam-comparison').innerText(),/19 \/ 48/);
  36. await page.getByRole('button',{name:'开始测试',exact:true}).click();await page.getByRole('button',{name:'苹果',exact:true}).click();await page.getByRole('button',{name:'不认识,下一题',exact:true}).click();
  37. await page.getByRole('button',{name:'重试保存',exact:true}).waitFor();assert.equal(calls.filter(c=>c.action==='vote_add').length,1);
  38. failSummary=false;await page.getByRole('button',{name:'重试保存',exact:true}).click();await page.getByText('测试结果已保存',{exact:true}).waitFor();assert.equal(calls.filter(c=>c.action==='vote_add').length,1);assert.equal(records.length,13);assert.equal(JSON.stringify(records.slice(1)),baseline);
  39. const saves=calls.filter(c=>c.action==='content_add');assert.equal(saves[0].requestKey,saves[1].requestKey);
  40. await page.getByRole('button',{name:'换一套再测',exact:true}).click();await page.getByRole('button',{name:'开始测试',exact:true}).waitFor();await page.getByRole('button',{name:'开始测试',exact:true}).click();await page.getByRole('button',{name:'苹果',exact:true}).click();await page.getByRole('button',{name:'苹果',exact:true}).click();await page.getByText('测试结果已保存',{exact:true}).waitFor();assert.equal(records.length,14);assert.equal(JSON.stringify(records.slice(2)),baseline);
  41. await page.reload();await page.locator('.exam-history-row').first().waitFor();await page.waitForFunction(()=>document.querySelector('.exam-comparison')?.textContent.includes('2 / 2'));assert.match(await page.locator('.exam-comparison').innerText(),/1 \/ 2/);
  42. for(const width of [390,360]){await page.setViewportSize({width,height:844});assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1));await page.screenshot({path:out+'/history-'+width+'.png',fullPage:true});}
  43. failHistory=true;await page.reload();await page.getByText('测试记录暂时无法读取',{exact:true}).waitFor();assert.equal(await page.getByText('还没有测试记录,完成测试后会保存在这里。',{exact:true}).count(),0);
  44. failHistory=false;await page.getByRole('button',{name:'重试读取记录',exact:true}).click();await page.locator('.exam-history-row').first().waitFor();assert.deepEqual(errors,[]);
  45. writeFileSync(out+'/result.json',JSON.stringify({passed:true,checks:['连续两次测试均新增并保留旧记录','保存失败重试不重复答卷且摘要复用请求编号','重新进入可对比两次成绩','分页与选择历史记录','读取失败不报无记录','360/390布局'],environment:'local production build and fixture API'},null,2));console.log('Exam history browser checks passed');
  46. }finally{await browser.close();await new Promise(r=>server.close(r));}