| import { chromium } from 'playwright'; |
| import fs from 'node:fs/promises'; |
|
|
| const root = new URL('../..', import.meta.url).pathname; |
| const url = process.argv[2] ?? 'https://mike0021-minicpm5-2b-webgpu-pi-http.static.hf.space/'; |
| const gist = 'https://gist.githubusercontent.com/mbostock/4063269/raw/5e72bffb17d12e15fb9da3a8b6794ac19db802a2/README.md'; |
| const result = { date: new Date().toISOString(), url, prompt: `Fetch ${gist} and tell me what kind of chart it describes.`, errors: [], requests: [] }; |
| const context = await chromium.launchPersistentContext(root + '/validation/http-browser-profile', { |
| 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(120_000); |
| const page = await context.newPage(); |
| page.on('pageerror', error => result.errors.push(error.message)); |
| context.on('request', request => { |
| if (request.url().startsWith('https://gist.githubusercontent.com/')) result.requests.push({ url: request.url(), method: request.method() }); |
| }); |
| let timer; |
| try { |
| await page.goto(url); |
| await page.waitForFunction(() => window.browserPi?.terminal); |
| await page.evaluate(() => window.browserPi.ready); |
| await page.waitForFunction(() => !window.browserPi.busy); |
| await page.waitForTimeout(300); |
| const loadStart = Date.now(); |
| await page.evaluate(() => window.browserPi.load()); |
| result.modelLoadMs = Date.now() - loadStart; |
| await page.waitForFunction(() => !window.browserPi.busy); |
| await page.locator('#new-chat').click(); |
| await page.waitForFunction(() => !window.browserPi.busy); |
| await page.waitForTimeout(300); |
| const start = Date.now(); |
| const pending = page.evaluate(prompt => window.browserPi.prompt(prompt), result.prompt); |
| try { |
| await Promise.race([pending, new Promise((_, reject) => { |
| timer = setTimeout(() => reject(Error('Gist trial exceeded 120 seconds')), 120_000); |
| })]); |
| } catch (error) { |
| await page.evaluate(() => window.browserPi.rpc('stop')); |
| await pending.catch(() => {}); |
| throw error; |
| } finally { clearTimeout(timer); } |
| result.elapsedMs = Date.now() - start; |
| result.state = await page.evaluate(() => window.browserPi.rpc('snapshot')); |
| result.calls = result.state.messages.filter(m => m.role === 'assistant').flatMap(m => m.content.filter(c => c.type === 'toolCall')); |
| result.toolResults = result.state.messages.filter(m => m.role === 'toolResult'); |
| result.final = result.state.messages.filter(m => m.role === 'assistant').at(-1)?.content.filter(c => c.type === 'text').map(c => c.text).join('\n') ?? ''; |
| result.passed = result.requests.some(r => r.url === gist) && |
| result.toolResults.some(t => !t.isError && t.toolName === 'bash' && /bubble charts/i.test(JSON.stringify(t.content))) && |
| /bubble chart/i.test(result.final) && !result.errors.length; |
| console.log(JSON.stringify({ passed: result.passed, elapsedMs: result.elapsedMs, final: result.final, calls: result.calls })); |
| } catch (error) { |
| result.failure = error.stack; |
| result.passed = false; |
| console.error(error.stack); |
| } finally { |
| clearTimeout(timer); |
| await fs.writeFile(root + '/validation/http-any-https.json', JSON.stringify(result, null, 2) + '\n'); |
| await context.close(); |
| if (!result.passed) process.exitCode = 1; |
| } |
|
|