|
@@ -2,6 +2,7 @@
|
|
|
// Run from repository root: DIRECT=1 RUNS=10 OUT=browser-direct node perf/recovery-performance-2026-11-14/benchmark.mjs /absolute/private/session-directory
|
|
// Run from repository root: DIRECT=1 RUNS=10 OUT=browser-direct node perf/recovery-performance-2026-11-14/benchmark.mjs /absolute/private/session-directory
|
|
|
// Never place session files or raw responses in Git. This script does not log request payloads or tokens.
|
|
// Never place session files or raw responses in Git. This script does not log request payloads or tokens.
|
|
|
import fs from 'node:fs';
|
|
import fs from 'node:fs';
|
|
|
|
|
+import {pageReadyGate} from './readiness.mjs';
|
|
|
import {createRequire} from 'node:module';
|
|
import {createRequire} from 'node:module';
|
|
|
import path from 'node:path';
|
|
import path from 'node:path';
|
|
|
const require=createRequire(path.resolve('package.json'));
|
|
const require=createRequire(path.resolve('package.json'));
|
|
@@ -18,20 +19,16 @@ try {for(let run=1;run<=count;run++) for(const cohort of (run%2?['teacher-home',
|
|
|
await context.addInitScript(s=>{localStorage.setItem('xiaoshu.angular.session',JSON.stringify(s));window.__long=[];performance.setResourceTimingBufferSize(2000);new PerformanceObserver(l=>window.__long.push(...l.getEntries().map(x=>x.duration))).observe({type:'longtask',buffered:true});},session);
|
|
await context.addInitScript(s=>{localStorage.setItem('xiaoshu.angular.session',JSON.stringify(s));window.__long=[];performance.setResourceTimingBufferSize(2000);new PerformanceObserver(l=>window.__long.push(...l.getEntries().map(x=>x.duration))).observe({type:'longtask',buffered:true});},session);
|
|
|
const page=await context.newPage(),cdp=await context.newCDPSession(page);
|
|
const page=await context.newPage(),cdp=await context.newCDPSession(page);
|
|
|
await cdp.send('Network.enable');await cdp.send('Network.emulateNetworkConditions',{offline:false,latency:20,downloadThroughput:4*1024*1024/8,uploadThroughput:3*1024*1024/8});
|
|
await cdp.send('Network.enable');await cdp.send('Network.emulateNetworkConditions',{offline:false,latency:20,downloadThroughput:4*1024*1024/8,uploadThroughput:3*1024*1024/8});
|
|
|
- const start=Date.now(),apis=[],pending=[],starts=new Map();let shellMs=null;
|
|
|
|
|
|
|
+ const start=Date.now(),apis=[],failedRequests=[],pending=[],starts=new Map();let shellMs=null;
|
|
|
page.on('request',req=>starts.set(req,Date.now()));
|
|
page.on('request',req=>starts.set(req,Date.now()));
|
|
|
|
|
+ page.on('requestfailed',req=>{if(!req.url().includes('/api/functions/'))return;let action='unknown';try{action=req.postDataJSON()?.params?.action||'preflight';}catch{}failedRequests.push({action,method:req.method(),ms:Date.now()-(starts.get(req)||start),error:req.failure()?.errorText||'unknown'});});
|
|
|
page.on('response',res=>{const req=res.request();if(!res.url().includes('/api/functions/')||req.method()==='OPTIONS')return;pending.push((async()=>{let action='unknown';try{action=req.postDataJSON()?.params?.action||'unknown'}catch{};await res.finished().catch(()=>{});let body;try{body=await res.body()}catch{};let d;try{d=JSON.parse(body)}catch{};apis.push({action,startMs:(starts.get(req)||start)-start,ms:Date.now()-(starts.get(req)||start),status:res.status(),retcode:d?.retcode,bytes:body?.length??null,timing:req.timing(),reviewMeta:d?.result?.legacyReviewSync?Object.fromEntries(Object.entries(d.result.legacyReviewSync).filter(([k])=>['state','pulled','written','sourceTotal','complete','conflicts'].includes(k))):undefined});})());});
|
|
page.on('response',res=>{const req=res.request();if(!res.url().includes('/api/functions/')||req.method()==='OPTIONS')return;pending.push((async()=>{let action='unknown';try{action=req.postDataJSON()?.params?.action||'unknown'}catch{};await res.finished().catch(()=>{});let body;try{body=await res.body()}catch{};let d;try{d=JSON.parse(body)}catch{};apis.push({action,startMs:(starts.get(req)||start)-start,ms:Date.now()-(starts.get(req)||start),status:res.status(),retcode:d?.retcode,bytes:body?.length??null,timing:req.timing(),reviewMeta:d?.result?.legacyReviewSync?Object.fromEntries(Object.entries(d.result.legacyReviewSync).filter(([k])=>['state','pulled','written','sourceTotal','complete','conflicts'].includes(k))):undefined});})());});
|
|
|
let ok=false,error=null,readyMs=null,metrics=null;
|
|
let ok=false,error=null,readyMs=null,metrics=null;
|
|
|
try {await page.goto('https://h5.xiaoshu.pro/pages/home/'+(course?'shop':'index'),{waitUntil:'domcontentloaded',timeout:45000});
|
|
try {await page.goto('https://h5.xiaoshu.pro/pages/home/'+(course?'shop':'index'),{waitUntil:'domcontentloaded',timeout:45000});
|
|
|
await page.waitForSelector(course?'.mobile-pagination':role==='teacher'?'.coach-hero':'.memory-hero',{timeout:45000});shellMs=Date.now()-start;
|
|
await page.waitForSelector(course?'.mobile-pagination':role==='teacher'?'.coach-hero':'.memory-hero',{timeout:45000});shellMs=Date.now()-start;
|
|
|
- await page.waitForFunction(({role,course})=>{const q=s=>document.querySelector(s);let valid=false,key='';
|
|
|
|
|
- if(course){const p=q('.mobile-pagination');valid=!!p&&!p.textContent.includes('—')&&!q('.center-state.error')&&!Array.from(document.querySelectorAll('button')).some(b=>b.textContent.trim()==='刷新课表'&&b.disabled);key=p?.textContent+'|'+document.querySelectorAll('[data-schedule-id]').length;}
|
|
|
|
|
- else if(role==='teacher'){valid=!!q('.coach-grid:not(.loading)')&&!!q('.coach-memory-summary')&&!q('.coach-hero-error')&&!q('.coach-memory-state.error')&&!q('button[aria-label="刷新抗遗忘任务"]:disabled');key=q('.coach-memory-summary')?.textContent+'|'+document.querySelectorAll('.coach-memory-list article').length;}
|
|
|
|
|
- else{valid=!!q('.memory-content')&&!!q('button[aria-label="刷新学习数据"]:not(:disabled)')&&!q('.hero-error');key=q('.student-stats')?.textContent+'|'+q('.today-panel')?.textContent;}
|
|
|
|
|
- if(!valid){window.__ready=null;return false}if(window.__ready?.key!==key){window.__ready={key,at:performance.now()};return false}return performance.now()-window.__ready.at>=500;
|
|
|
|
|
- },{role,course},{timeout:65000});readyMs=Date.now()-start;ok=true;
|
|
|
|
|
|
|
+ await page.waitForFunction(pageReadyGate,{role,course},{timeout:65000});readyMs=Date.now()-start;ok=true;
|
|
|
}catch(e){error=String(e.message).split('\n')[0];}
|
|
}catch(e){error=String(e.message).split('\n')[0];}
|
|
|
- try{metrics=await page.evaluate(()=>({longTaskMs:window.__long?.reduce((a,b)=>a+b,0)||0,resources:performance.getEntriesByType('resource').map(r=>({name:r.name.split('?')[0],startMs:r.startTime,endMs:r.responseEnd,ms:r.duration,transferBytes:r.transferSize,encodedBytes:r.encodedBodySize,type:r.initiatorType})),reviewRows:document.querySelectorAll('.coach-memory-list article').length,scheduleRows:document.querySelectorAll('[data-schedule-id]').length,errors:Array.from(document.querySelectorAll('.center-state.error,.coach-hero-error,.coach-memory-state.error,.hero-error')).map(e=>e.textContent)}));}catch{}
|
|
|
|
|
- await Promise.allSettled(pending);const row={cohort,run,at:new Date().toISOString(),ok,error,readyMs,shellMs,elapsedMs:Date.now()-start,apis,metrics};results.push(row);fs.writeFileSync(root+'/'+out+'.json',JSON.stringify(results,null,2),{mode:0o600});console.log(JSON.stringify({cohort,run,ok,readyMs,shellMs,apis:apis.map(({action,ms,status,retcode})=>({action,ms,status,retcode})),error}));await context.close();
|
|
|
|
|
|
|
+ try{metrics=await page.evaluate(()=>({longTaskMs:window.__long?.reduce((a,b)=>a+b,0)||0,resources:performance.getEntriesByType('resource').map(r=>({name:r.name.split('?')[0],startMs:r.startTime,endMs:r.responseEnd,ms:r.duration,transferBytes:r.transferSize,encodedBytes:r.encodedBodySize,type:r.initiatorType})),reviewRows:document.querySelectorAll('.coach-memory-list article').length,scheduleRows:document.querySelectorAll('[data-schedule-id]').length,errors:Array.from(document.querySelectorAll('.center-state.error,.coach-hero-error,.coach-memory-state.error,.hero-error,.memory-hero + p[role="status"]')).map(e=>e.textContent)}));}catch{}
|
|
|
|
|
+ await Promise.allSettled(pending);const row={cohort,run,at:new Date().toISOString(),ok,error,readyMs,shellMs,elapsedMs:Date.now()-start,apis,failedRequests,metrics};results.push(row);fs.writeFileSync(root+'/'+out+'.json',JSON.stringify(results,null,2),{mode:0o600});console.log(JSON.stringify({cohort,run,ok,readyMs,shellMs,apis:apis.map(({action,ms,status,retcode})=>({action,ms,status,retcode})),error}));await context.close();
|
|
|
if(results.slice(-4).filter(r=>!r.ok).length>=3)throw Error('Stop after repeated unavailable pages');
|
|
if(results.slice(-4).filter(r=>!r.ok).length>=3)throw Error('Stop after repeated unavailable pages');
|
|
|
}}finally{await browser.close();}
|
|
}}finally{await browser.close();}
|