File size: 6,449 Bytes
f104170 9c380c2 f104170 9c380c2 f104170 9c380c2 f104170 9c380c2 f104170 | 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 | import { chromium } from 'playwright';
import fs from 'node:fs/promises';
import assert from 'node:assert/strict';
const root = new URL('../..', import.meta.url).pathname;
const url = process.argv[2] ?? 'https://mike0021-minicpm5-2b-webgpu-pi-http.static.hf.space/';
const result = { date: new Date().toISOString(), url, cases: [], 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(30_000);
const page = await context.newPage();
page.on('pageerror', e => result.errors.push(e.message));
context.on('request', r => { if (!r.url().startsWith(new URL(url).origin)) result.requests.push({ url: r.url(), method: r.method() }); });
const rpc = (action, args) => page.evaluate(({ action, args }) => window.browserPi.rpc(action, args), { action, args });
const save = () => fs.writeFile(root + '/validation/http-shell-live.json', JSON.stringify(result, null, 2) + '\n');
const cases = [
['new-host-json', 'curl -fsS https://dummyjson.com/products/1 -o product.json && jq -r .id product.json', r => r.exitCode === 0 && r.stdout.trim() === '1'],
['public-gist', 'curl -fsS https://gist.githubusercontent.com/mbostock/4063269/raw/b111d979d7fcd0fabb554cccd37caf2691f31706/flare.csv -o gist.csv && head -n 1 gist.csv', r => r.exitCode === 0 && r.stdout.trim() === 'id,value'],
['json-get-save', 'curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json && jq -r .title todo.json', r => r.exitCode === 0 && r.stdout.includes('delectus aut autem')],
['github-pipeline', "set -o pipefail; curl -fsS https://api.github.com/repos/huggingface/transformers.js | jq -r '.full_name, .license.spdx_id'", r => r.exitCode === 0 && r.stdout.includes('huggingface/transformers.js')],
['hf-metadata', 'set -o pipefail; curl -fsS https://huggingface.co/api/models/openbmb/MiniCPM5-2B | jq -r .id', r => r.exitCode === 0 && /openbmb\/MiniCPM5-2B/i.test(r.stdout)],
['raw-github', 'curl -fsS https://raw.githubusercontent.com/huggingface/transformers.js/main/package.json -o package.json && jq -r .name package.json', r => r.exitCode === 0 && r.stdout.includes('@huggingface/transformers')],
['head', 'curl -I https://jsonplaceholder.typicode.com/todos/1', r => r.exitCode === 0 && r.stdout.includes('200')],
['post-json', `set -o pipefail; curl -fsS https://httpbin.org/post -H 'Content-Type: application/json' -d '{"demo":"browser-http","value":144}' | jq -c .json`, r => r.exitCode === 0 && r.stdout.includes('browser-http') && r.stdout.includes('144')],
['request-headers', "set -o pipefail; curl -fsS https://httpbin.org/headers -H 'X-Demo: browser-http' | jq -r '.headers[\"X-Demo\"]'", r => r.exitCode === 0 && r.stdout.trim() === 'browser-http'],
['http-404', 'curl -fsS https://jsonplaceholder.typicode.com/does-not-exist', r => r.exitCode !== 0 && /404/.test(r.stderr)],
['http-rejected', 'curl -fsS http://example.com/', r => r.exitCode !== 0 && /Only HTTPS/.test(r.stderr)],
['cors-failure', 'curl -fsS https://huggingface.co/', r => r.exitCode !== 0 && /CORS/.test(r.stderr)],
['redirect-failure', 'curl -fsSL https://httpbin.org/redirect-to?url=https%3A%2F%2Fhttpbin.org%2Fget', r => r.exitCode !== 0 && /redirect/i.test(r.stderr)],
['timeout', 'curl -fsS --max-time 1 https://httpbin.org/delay/5', r => r.exitCode !== 0 && /timed out/i.test(r.stderr)],
['failed-pipeline', 'set -o pipefail; curl -fsS http://example.com/ | jq .', r => r.exitCode !== 0],
];
try {
await page.goto(url); await page.waitForFunction(() => window.browserPi?.terminal); await page.evaluate(() => window.browserPi.ready);
assert.match(await page.locator('.workspace-note').textContent(), /any HTTPS URL/);
for (const [name, command, check] of cases) {
const start = Date.now(); const response = await rpc('shell', { command });
const entry = { name, command, elapsedMs: Date.now() - start, stdout: response.stdout, stderr: response.stderr, exitCode: response.exitCode, passed: check(response) };
result.cases.push(entry); console.log(JSON.stringify(entry)); await save();
}
const start = Date.now(); const pending = rpc('shell', { command: 'curl -fsS https://httpbin.org/delay/7' });
await page.waitForFunction(() => window.browserPi.busy); await page.waitForTimeout(200); await rpc('stop');
let cancelled; try { cancelled = await pending; } catch(e) { cancelled = { stderr: e.message, exitCode: 1 }; }
result.cases.push({ name: 'cancel', elapsedMs: Date.now() - start, stdout: cancelled.stdout, stderr: cancelled.stderr, exitCode: cancelled.exitCode, passed: cancelled.exitCode !== 0 && Date.now() - start < 2500 });
await page.locator('#command').fill('curl -fsS https://jsonplaceholder.typicode.com/todos/1 -o todo.json && jq . todo.json');
await page.locator('#shell-run').click();
await page.waitForFunction(() => !window.browserPi.busy && document.querySelector('.shell-entry:last-child .shell-status.success'));
await page.screenshot({ path: root + '/validation/http-desktop.png', fullPage: true });
await page.reload(); await page.waitForFunction(() => window.browserPi); await page.evaluate(() => window.browserPi.ready);
const saved = await rpc('read', { path: '/workspace/todo.json' });
result.cases.push({ name: 'file-survives-reload', passed: JSON.parse(saved).title === 'delectus aut autem' });
await page.setViewportSize({ width: 390, height: 844 });
await page.screenshot({ path: root + '/validation/http-mobile.png', fullPage: true });
result.mobileOverflow = await page.evaluate(() => document.documentElement.scrollWidth > innerWidth);
result.device = await page.evaluate(async () => { const a = await navigator.gpu?.requestAdapter({powerPreference:'high-performance'}); return a ? { vendor:a.info.vendor, architecture:a.info.architecture, features:[...a.features] } : null; });
result.passed = result.cases.every(c => c.passed) && !result.errors.length && !result.mobileOverflow;
console.log('SUMMARY', JSON.stringify({passed:result.passed, cases:result.cases.length, device:result.device, errors:result.errors}));
} catch (e) { result.failure = e.stack; console.log('FAIL',e.stack); process.exitCode=1; }
finally { await save(); await context.close(); }
|