verify-development-board.mjs 6.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import {chromium} from 'file:///C:/Users/JSHY/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright/index.mjs';
  2. import fs from 'node:fs/promises';
  3. import assert from 'node:assert/strict';
  4. const base='http://127.0.0.1:4183';
  5. const out='docs/development-board-20261108';
  6. await fs.mkdir(out,{recursive:true});
  7. const browser=await chromium.launch({executablePath:'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe',headless:true});
  8. const report={ok:false,checkedAt:new Date().toISOString(),base,layouts:[],checks:[],pageErrors:[],failedResponses:[],
  9. realHardware:false,responseOverrides:0,paidCalls:0,productionWrites:0};
  10. let current;
  11. const wait=async p=>{
  12. await p.locator('industry-development .dr-intro').waitFor();
  13. await p.waitForFunction(async()=>{const p=document.querySelector('kf-app')?.renderRoot.querySelector('view-industry');if(!p||p._loading||!p._data)return false;await p.updateComplete;return !p._error;});
  14. };
  15. const dimensions=async(p,width,section)=>{
  16. const metrics=await p.evaluate(()=>({innerWidth,clientWidth:document.documentElement.clientWidth,scrollWidth:document.documentElement.scrollWidth}));
  17. assert.equal(metrics.innerWidth,width);assert.ok(metrics.scrollWidth<=metrics.clientWidth,JSON.stringify(metrics));
  18. const bad=await p.locator('industry-development').evaluate(root=>[...root.renderRoot.querySelectorAll('*')].filter(e=>{
  19. if(e.closest('dialog:not([open])'))return false;
  20. const r=e.getBoundingClientRect();return r.width>0&&r.height>0&&(r.left< -1||r.right>document.documentElement.clientWidth+1);
  21. }).map(e=>({tag:e.tagName,cls:e.className,text:e.textContent.slice(0,60)})).slice(0,10));
  22. assert.deepEqual(bad,[],section+' '+width);report.layouts.push({width,section,...metrics,outOfBounds:bad.length});
  23. };
  24. const check=name=>{report.checks.push(name);console.log('PASS '+name);};
  25. try{
  26. for(const [width,height,mobile] of [[1440,900,false],[1024,768,false],[768,1024,false],[430,932,true],[390,844,true],[360,800,true],[320,568,true],[844,390,true]]){
  27. const context=await browser.newContext({viewport:{width,height},isMobile:mobile,hasTouch:mobile,permissions:['clipboard-read','clipboard-write'],deviceScaleFactor:1});
  28. const p=await context.newPage();current=p;
  29. p.on('pageerror',e=>report.pageErrors.push(e.message));
  30. p.on('response',r=>{if(r.status()>=400)report.failedResponses.push({url:r.url(),status:r.status()});});
  31. await p.goto(base+'/industry?tab=development');await wait(p);
  32. assert.equal(await p.locator('[data-development-direction]').count(),4);
  33. assert.equal(await p.locator('industry-development blockquote').count(),2);
  34. if([1440,390,320].includes(width))await p.screenshot({path:out+'/directions-'+width+'.png'});
  35. for(const tab of ['directions','categories','processes','materials','standards']){
  36. await p.locator('[data-development-tab='+tab+']').click();
  37. await dimensions(p,width,tab);
  38. const counts={categories:16,processes:3,materials:14,standards:10};
  39. if(tab==='categories')assert.equal(await p.locator('.dr-axis .dr-category').count(),12);
  40. if(['processes','materials','standards'].includes(tab))assert.equal(await p.locator('.dr-tab-content [data-reference]').count(),counts[tab]);
  41. if([1440,390].includes(width)&&['categories','materials','standards'].includes(tab))await p.screenshot({path:out+'/'+tab+'-'+width+'.png'});
  42. }
  43. await p.locator('[data-development-tab=materials]').click();
  44. await p.locator('[data-material-group="营养强化"]').click();assert.equal(await p.locator('.dr-material-grid>.dr-card').count(),2);
  45. await p.locator('[data-reference="fortifier-维生素B1"]').click();
  46. await p.locator('.dr-detail[open]').waitFor();assert.match(await p.locator('.dr-detail').innerText(),/没有核实目标食品类别/);
  47. await dimensions(p,width,'source-dialog');
  48. await p.locator('[data-close-reference]').click();
  49. await p.locator('[data-material-group="全部"]').click();
  50. await p.getByPlaceholder('搜索原料或作用').fill('硫胺素');assert.equal(await p.locator('.dr-material-grid>.dr-card').count(),1);
  51. await p.getByPlaceholder('搜索原料或作用').fill('无此原料');assert.equal(await p.locator('.dr-material-grid>.dr-card').count(),0);
  52. assert.match(await p.locator('.dr-empty').innerText(),/没有匹配/);
  53. await p.locator('[data-development-tab=directions]').click();
  54. for(const id of ['spice','texture','nutrition','seasoning']){
  55. await p.locator('[data-development-direction='+id+']').click();
  56. assert.equal(await p.locator('industry-development blockquote').count(),id==='nutrition'?0:2);
  57. await dimensions(p,width,'direction-'+id);
  58. }
  59. await p.locator('[data-development-copy]').click();
  60. const text=await p.evaluate(()=>navigator.clipboard.readText());
  61. assert.ok(text.includes('KfMaterial/')&&text.includes('KfSource/')&&text.includes('3279276824734890')&&text.includes('待验证设想'));
  62. await p.locator('.dr-coverage>summary').click();
  63. assert.match(await p.locator('.dr-coverage').innerText(),/快照/);
  64. await dimensions(p,width,'coverage');
  65. if(mobile){
  66. const controls=p.locator('industry-development button:visible');
  67. for(let i=0;i<await controls.count();i++){
  68. const r=await controls.nth(i).boundingBox();assert.ok(r.height>=43.5,'touch target height');
  69. }
  70. }
  71. check('five sections, four directions, references, filter, search, copy and layout '+width);
  72. if(width===1440){
  73. await p.locator('industry-development a').filter({hasText:'查看原评'}).first().click();
  74. await p.waitForURL(/\/library\/reviews\//);
  75. await p.locator('view-library').waitFor();check('review navigation validates the bound VOC generation');
  76. }
  77. await context.close();
  78. }
  79. assert.deepEqual(report.pageErrors,[]);assert.deepEqual(report.failedResponses,[]);
  80. report.ok=true;
  81. }catch(e){report.error=e.stack;console.error(e);if(current)await current.screenshot({path:out+'/failure.png'}).catch(()=>{});process.exitCode=1;}
  82. finally{await browser.close();await fs.writeFile(out+'/browser-acceptance.json',JSON.stringify(report,null,2)+'\n');}