test_data_quality_ui.cjs 4.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. const assert = require('node:assert/strict');
  2. const fs = require('node:fs');
  3. const path = require('node:path');
  4. const http = require('node:http');
  5. const { chromium } = require('playwright');
  6. const root = path.resolve(__dirname, '..');
  7. let mode = 'issues';
  8. const records = ['人员证书', '项目信息'].map((template, index) => ({template, file:template+'.xlsx', sheet:'数据', row:7,
  9. fields:['编号'], message:'缺少主键字段:编号', action:'已跳过,未进入知识图谱',
  10. data:{编号:null, 名称:index ? '项目示例乙' : '证书示例甲', 备注:'<img src=x onerror=alert(1)>', 数量:0}}));
  11. const server = http.createServer((req, res) => {
  12. const url = new URL(req.url, 'http://localhost');
  13. if (url.pathname === '/api/data-quality') {
  14. res.setHeader('Content-Type','application/json; charset=utf-8');
  15. if (mode === 'error') { res.statusCode=503; res.end(JSON.stringify({detail:'报告暂不可用'})); return; }
  16. const payload = mode === 'zero' ? {data_version:'v2',total:0,datasets:{},records:[]} :
  17. {data_version:'v1',total:2,datasets:{人员证书:1,项目信息:1},records};
  18. if (!url.searchParams.has('details')) { const {records, ...summary}=payload; res.end(JSON.stringify(summary)); }
  19. else res.end(JSON.stringify(payload));
  20. return;
  21. }
  22. if (url.pathname.startsWith('/output/')) { res.setHeader('Content-Type','application/json'); res.end(JSON.stringify({nodes:[],relations:[],meta:{build_id:'v1'}})); return; }
  23. const file = path.join(root,'html',url.pathname === '/' ? 'knowledge_graph_3d.html' : url.pathname);
  24. if (!file.startsWith(path.join(root,'html')) || !fs.existsSync(file)) {res.statusCode=404; res.end();return;}
  25. res.setHeader('Content-Type',file.endsWith('.js') ? 'application/javascript; charset=utf-8' : file.endsWith('.css') ? 'text/css; charset=utf-8' : 'text/html; charset=utf-8');
  26. res.end(fs.readFileSync(file));
  27. });
  28. (async () => {
  29. await new Promise(resolve => server.listen(0,'127.0.0.1',resolve));
  30. const browser = await chromium.launch({headless:true, channel:'msedge'});
  31. try {
  32. const page = await browser.newPage({viewport:{width:1280,height:900}});
  33. const base = `http://127.0.0.1:${server.address().port}`;
  34. let dialogs=0; page.on('dialog', d => { dialogs++; d.dismiss(); });
  35. for (const entry of ['/index.html','/']) {
  36. mode='issues'; await page.goto(base+entry);
  37. const alert=page.locator('#dataQualityAlert');
  38. await alert.waitFor({state:'visible'}); assert.match(await alert.textContent(), /2/);
  39. await alert.click();
  40. await page.getByRole('button',{name:'项目信息.xlsx (1)',exact:true}).waitFor();
  41. assert.match(await page.locator('.dq-card h3').textContent(), /人员证书.xlsx.*第 7 行/);
  42. assert.equal(await page.locator('.dq-data dd').count(),4);
  43. assert.equal(await page.locator('.dq-data dd.dq-missing').textContent(),'(空)');
  44. assert.equal(await page.locator('.dq-data img').count(),0);
  45. await page.getByRole('button',{name:'项目信息.xlsx (1)',exact:true}).click();
  46. assert.match(await page.locator('.dq-card h3').textContent(),/项目信息.xlsx/);
  47. assert.match(await page.locator('.dq-card').textContent(),/项目示例乙/);
  48. fs.mkdirSync(path.join(root,'.runtime/qa'),{recursive:true});
  49. if(entry==='/') await page.screenshot({path:path.join(root,'.runtime/qa/data-quality-desktop.png')});
  50. await page.keyboard.press('Escape');
  51. assert.equal(await page.locator('#dataQualityDialog').evaluate(el=>el.open),false);
  52. }
  53. await page.setViewportSize({width:390,height:844}); await page.locator('#dataQualityAlert').click();
  54. await page.locator('.dq-card').waitFor();
  55. assert.ok(await page.locator('.dq-dialog').evaluate(el=>el.scrollWidth<=el.clientWidth));
  56. await page.screenshot({path:path.join(root,'.runtime/qa/data-quality-mobile.png')});
  57. mode='zero'; await page.evaluate(()=>window.dispatchEvent(new Event('focus')));
  58. await page.locator('#dataQualityAlert').waitFor({state:'hidden'});
  59. await page.getByText('当前版本没有缺少主键的记录。',{exact:true}).waitFor();
  60. await page.keyboard.press('Escape'); mode='error'; await page.evaluate(()=>window.dispatchEvent(new Event('focus')));
  61. await page.getByText('数据提醒暂不可用',{exact:true}).waitFor();
  62. await page.locator('#dataQualityAlert').click(); await page.getByRole('button',{name:'重新加载',exact:true}).waitFor();
  63. mode='issues'; await page.getByRole('button',{name:'重新加载',exact:true}).click(); await page.locator('.dq-card').waitFor();
  64. assert.equal(dialogs,0);
  65. console.log('PASS: both pages, file grouping, full fields, missing highlight, text escaping, Escape, mobile, zero/version refresh, error/retry');
  66. } finally { await browser.close(); await new Promise(resolve=>server.close(resolve)); }
  67. })().catch(error=>{console.error(error);process.exitCode=1;server.close();});