File size: 7,851 Bytes
39371ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import { chromium } from 'playwright';
import fs from 'node:fs/promises';
import assert from 'node:assert/strict';

const url = process.argv[2] ?? 'http://127.0.0.1:4173/?source=local';
const label = process.argv[3] ?? 'agent-runtime-meter';
const result = { date: new Date().toISOString(), url, checks: [], layouts: [], errors: [] };
const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true });
let context, page;
try {
  for (const [width, height] of [[1440,1000],[1100,800],[1000,800],[851,800],[850,800],[390,844],[320,568],[390,420]]) {
    const page = await browser.newPage({ viewport: { width, height }, hasTouch: width < 500, deviceScaleFactor: 2 });
    page.on('pageerror', e => result.errors.push(e.message));
    await page.goto(url); await page.waitForFunction(() => window.browserPi?.runtimeMeter); await page.evaluate(() => window.browserPi.ready);
    assert.equal(await page.locator('.runtime-engine').innerText(), 'WebGPU');
    assert.equal(await page.locator('.runtime-value').innerText(), 'Idle');
    for (const event of [
      { type: 'busy', action: 'load', cachedOnly: true },
      { type: 'load_progress', phase: 'download', loaded: 184, total: 1840 },
      { type: 'inference_activity', phase: 'prefill' },
      ...[18,28,23,36,30,38,32].map(rate => ({ type: 'inference_activity', phase: 'decode', rate })),
    ]) await page.evaluate(event => window.browserPi.runtimeMeter.update(event), event);
    assert.equal(await page.locator('.runtime-value').textContent(), '32 tok/s');
    assert(await page.locator('.runtime-sparkline').isVisible());
    await page.waitForFunction(() => {
      const meter = document.querySelector('#runtime-meter');
      const content = meter.querySelector('.runtime-content');
      return Math.abs(meter.getBoundingClientRect().width - Math.ceil(content.getBoundingClientRect().width) - 2 * parseFloat(getComputedStyle(meter).paddingLeft)) < 1;
    });
    const layout = await page.evaluate(() => {
      const rect = selector => { const r=document.querySelector(selector).getBoundingClientRect();return {x:r.x,y:r.y,right:r.right,bottom:r.bottom,width:r.width,height:r.height}; };
      return { header:rect('header'), brand:rect('.brand'), meter:rect('#runtime-meter'), model:rect('.model-panel'), value:rect('.runtime-value'), overflow:document.documentElement.scrollWidth>innerWidth };
    });
    for (const item of [layout.brand,layout.meter,layout.model]) assert(item.x>=layout.header.x&&item.right<=layout.header.right&&item.y>=layout.header.y&&item.bottom<=layout.header.bottom,JSON.stringify({width,...layout}));
    assert(layout.brand.right<=layout.meter.x);
    assert(layout.value.right<=layout.meter.right-8);
    if(width>850)assert(layout.meter.right<=layout.model.x);
    assert(!layout.overflow); result.layouts.push({width,height,...layout});
    await page.locator('header').screenshot({path:`../validation/${label}-fixture-${width}x${height}.png`});
    await page.evaluate(()=>window.browserPi.runtimeMeter.update({type:'agent_event',event:{type:'tool_execution_start',toolName:'bash'}}));
    assert.equal(await page.locator('.runtime-value').textContent(),'Running bash…'); assert(!await page.locator('.runtime-sparkline').isVisible());
    assert.equal(await page.locator('.runtime-engine').textContent(),'JavaScript');
    await page.evaluate(()=>window.browserPi.runtimeMeter.update({type:'idle'}));
    assert.equal(await page.locator('.runtime-value').textContent(),'Idle');
    assert.equal(await page.locator('.runtime-engine').textContent(),'WebGPU');
    await page.evaluate(()=>window.browserPi.runtimeMeter.update({type:'inference_activity',phase:'decode',rate:32}));
    await page.waitForFunction(()=>document.querySelector('.runtime-value').textContent==='Generating…');
    assert(!await page.locator('.runtime-sparkline').isVisible());
    await page.close();
  }
  result.checks.push('Eight viewport fixtures fit the header; measured bars, tool/idle states and stale-sample handling render correctly');
  await browser.close();

  context=await chromium.launchPersistentContext(new URL('../../validation/agent-browser-profile',import.meta.url).pathname,{
    executablePath:'/usr/bin/chromium',headless:true,viewport:{width:1440,height:1000},
    args:['--enable-unsafe-webgpu','--use-angle=vulkan','--enable-features=Vulkan','--disable-vulkan-surface'],
  });
  context.setDefaultTimeout(180_000); page=await context.newPage(); page.on('pageerror',e=>result.errors.push(e.message));
  await page.goto(url); await page.waitForFunction(()=>window.browserPi?.runtimeMeter); await page.evaluate(()=>window.browserPi.ready);
  await page.evaluate(()=>document.querySelector('#new-chat').onclick());
  const nonce='METER-'+crypto.randomUUID().slice(0,8);
  await page.evaluate(nonce=>window.browserPi.rpc('write',{path:'/workspace/meter-check.txt',content:nonce}),nonce);
  if(!await page.evaluate(()=>window.browserPi.rpc('cache_status')))await page.evaluate(()=>window.browserPi.load());
  await page.evaluate(()=>{
    window.meterStates=[];
    new MutationObserver(()=>window.meterStates.push({state:document.querySelector('#runtime-meter').dataset.state,value:document.querySelector('.runtime-value').textContent})).observe(document.querySelector('#runtime-meter'),{attributes:true,attributeFilter:['data-state']});
  });
  await page.locator('.xterm-helper-textarea').focus();await page.keyboard.type('Read meter-check.txt using the read tool and tell me its exact contents.');await page.locator('#send').click();
  await page.waitForFunction(()=>window.browserPi.diagnostics.runtimeSamples.some(s=>s.phase==='decode'&&s.rate>0));
  await page.locator('header').screenshot({path:`../validation/${label}-live.png`});
  await page.waitForFunction(()=>!window.browserPi.busy&&window.browserPi.diagnostics.events.includes('agent_end'));
  const state=await page.evaluate(()=>window.browserPi.rpc('snapshot'));
  result.states=await page.evaluate(()=>window.meterStates);
  result.samples=await page.evaluate(()=>window.browserPi.diagnostics.runtimeSamples);
  assert(result.states.some(s=>s.state==='prefill'));
  assert(result.states.some(s=>s.state==='generating'&&/tok\/s/.test(s.value)));
  assert(result.states.some(s=>s.state==='tool'));
  assert(result.samples.filter(s=>s.rate!==undefined&&s.rate!==null).every(s=>Number.isFinite(s.rate)&&s.rate>=0));
  assert(state.messages.filter(m=>m.role==='assistant').at(-1).content.some(c=>c.text?.includes(nonce)));
  assert.equal(await page.locator('#filename').textContent(), '/workspace/meter-check.txt');
  assert.equal(await page.locator('#editor').inputValue(), nonce);
  assert.equal(await page.locator('.runtime-value').textContent(),'Ready'); assert(!await page.locator('.runtime-sparkline').isVisible());
  result.checks.push('Actual WebGPU token callbacks drive live rates, with distinct prefill, tool and idle states and a verified file result');
  await page.locator('.xterm-helper-textarea').focus();await page.keyboard.type('Count from 1 to 200.');await page.locator('#send').click();
  await page.waitForFunction(()=>document.querySelector('#runtime-meter').dataset.state==='generating');await page.locator('#stop').click();
  await page.waitForFunction(()=>!window.browserPi.busy);
  assert.equal(await page.locator('.runtime-value').textContent(),'Ready'); assert(!await page.locator('.runtime-sparkline').isVisible());
  result.checks.push('Stopping generation clears the live rate and chart');
  assert.deepEqual(result.errors,[]);result.passed=true;console.log(JSON.stringify({passed:true,checks:result.checks,samples:result.samples.length}));
} catch(error){result.failure=error.stack;throw error;}
finally{await fs.writeFile(`../validation/${label}.json`,JSON.stringify(result,null,2));await context?.close();await browser.close();}