verify-workshop-quality-r13-browser.mjs 5.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import fs from 'node:fs/promises';
  2. import path from 'node:path';
  3. import assert from 'node:assert/strict';
  4. import {pathToFileURL} from 'node:url';
  5. import {chromium} from '../work/workshop-manual-tools/node_modules/playwright/index.mjs';
  6. const root=path.resolve('docs/workshop-quality-r13-20261110');
  7. const prompts=JSON.parse(await fs.readFile(path.join(root,'prompts-and-outputs.json'),'utf8')).steps;
  8. const browser=await chromium.launch({headless:true,executablePath:'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'});
  9. const context=await browser.newContext({permissions:['clipboard-read','clipboard-write']});
  10. const page=await context.newPage();
  11. const errors=[];page.on('pageerror',e=>errors.push(e.message));
  12. const cases=[];
  13. async function overflow(name){
  14. const d=await page.evaluate(()=>({viewport:innerWidth,width:document.documentElement.scrollWidth}));
  15. assert.ok(d.width<=d.viewport,name+' '+JSON.stringify(d));
  16. }
  17. try{
  18. let copies=0,exact=0,sampleLinks=0;
  19. for(const rel of ['康师傅工作坊操作手册-r13本地修订.html','manual/index.html']){
  20. await page.goto(pathToFileURL(path.join(root,rel)).href);
  21. assert.equal(await page.locator('.nav-item').count(),15);
  22. for(const width of [1440,390]){
  23. await page.setViewportSize({width,height:1000});
  24. for(const s of prompts){
  25. await page.evaluate(id=>location.hash=id,s.id);
  26. await page.waitForFunction(id=>document.querySelector('.nav-item[aria-current="step"]')?.getAttribute('href')==='#'+id,s.id);
  27. assert.equal(await page.locator('#prompt-text').textContent(),s.fullPrompt,s.id);exact++;
  28. assert.ok(await page.locator('#prompt-text').isVisible());
  29. assert.equal(await page.locator('.action-copy').count(),0);
  30. await page.locator('#copy-prompt').click();
  31. const text=await page.evaluate(()=>navigator.clipboard.readText());
  32. assert.equal(text.replaceAll('\r\n','\n'),s.fullPrompt.replaceAll('\r\n','\n'));copies++;
  33. if(s.example){
  34. const links=await page.locator('.example-link a').evaluateAll(els=>els.map(el=>el.href));
  35. assert.equal(links.length,2,s.id);
  36. assert.equal(links[0],pathToFileURL(path.join(root,s.example)).href);sampleLinks++;
  37. }
  38. await overflow(rel+' '+s.id+' '+width);
  39. }
  40. if(!rel.startsWith('manual/')){
  41. await page.evaluate(()=>location.hash='matrix');
  42. await page.waitForFunction(()=>document.querySelector('#prompt-text').textContent.includes('需求与具体供给'));
  43. await page.screenshot({path:path.join(root,`手册-${width}.png`),fullPage:true});
  44. }
  45. }
  46. }
  47. cases.push({check:'all-prompts-exact-visible-and-copyable',comparisons:exact,copies,sampleLinks,passed:true});
  48. const reports=(await fs.readdir(path.join(root,'reports'))).filter(n=>n.endsWith('.html')).map(n=>'reports/'+n);
  49. const others=['index.html','案例报告合集.html','旧稿逐项复核.html','参考学习与类型对应.html','选材与分析规则.html','证据底表.html','商业回推演算器.html'];
  50. for(const rel of [...reports,...others]){
  51. await page.goto(pathToFileURL(path.join(root,rel)).href);
  52. for(const width of [1440,390]){
  53. await page.setViewportSize({width,height:1000});await overflow(rel+' '+width);
  54. if(['index.html','reports/proposal.html','reports/matrix.html','商业回推演算器.html'].includes(rel))
  55. await page.screenshot({path:path.join(root,rel.replaceAll('/','-').replace('.html','')+`-${width}.png`),fullPage:rel!=='index.html'});
  56. }
  57. }
  58. cases.push({check:'all-local-report-pages-fit-desktop-and-mobile',pages:reports.length+others.length,widths:[1440,390],passed:true});
  59. await page.goto(pathToFileURL(path.join(root,'商业回推演算器.html')).href);
  60. assert.match(await page.locator('#model-state').textContent(),/输入未齐/);
  61. assert.equal(await page.evaluate(()=>typeof window.modelResult),'undefined');
  62. await page.locator('#example').click();
  63. const result=await page.evaluate(()=>window.modelResult);
  64. assert.equal(result.value,50);assert.equal(result.breakEven,2000);assert.equal(result.origin,'synthetic');assert.equal(result.empiricalVerified,false);
  65. assert.equal(await page.locator('#sensitivity tbody tr').count(),5);
  66. await page.locator('#Rn').fill('0');
  67. assert.equal(await page.evaluate(()=>window.modelResult.breakEven),null);
  68. await page.locator('#Qn').fill('0.5');
  69. assert.match(await page.locator('#model-state').textContent(),/非负整数/);
  70. await page.locator('#reset').click();
  71. assert.match(await page.locator('#model-state').textContent(),/输入未齐/);
  72. assert.equal(await page.locator('input').evaluateAll(els=>els.every(el=>el.value==='')),true);
  73. cases.push({check:'blank-financial-model-and-explicit-synthetic-sensitivity',passed:true,companyDataUsed:false});
  74. await page.setViewportSize({width:1440,height:1000});
  75. await page.goto(pathToFileURL(path.join(root,'reports/proposal.html')).href);
  76. const r05=page.locator('article a', {hasText:'R05'}).first();
  77. await r05.click();assert.match(page.url(),/证据底表\.html#R05|%E8%AF%81%E6%8D%AE%E5%BA%95%E8%A1%A8\.html#R05/);
  78. assert.match(await page.locator('#R05').textContent(),/R05/);
  79. cases.push({check:'proposal-citation-opens-exact-raw-quote',passed:true});
  80. assert.deepEqual(errors,[]);
  81. const resultFile={ok:true,cases,scriptErrors:errors,productionWrites:0,hostActualLoading:false,realEmployeeTrial:false,completeCourseEmpiricalAcceptance:false};
  82. await fs.writeFile(path.join(root,'浏览器核验.json'),JSON.stringify(resultFile,null,2)+'\n');
  83. console.log(JSON.stringify(resultFile));
  84. }finally{await browser.close()}