MiniCPM5-2B-WebGPU-Pi-HTTP / app /tests /space-embedded.mjs
Mike0021's picture
Enable browser HTTP commands with CORS-aware errors and bounded requests
39371ea verified
Raw History Blame Contribute Delete
5.98 kB
import { chromium } from 'playwright';
import fs from 'node:fs/promises';
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import path from 'node:path';
// Playwright 1.58 disables ThirdPartyStoragePartitioning by default. Restore
// Chromium's normal partitioning for a representative cross-site iframe test.
const require = createRequire(import.meta.url);
const { chromiumSwitches } = require(path.join(path.dirname(require.resolve('playwright-core/package.json')), 'lib/server/chromium/chromiumSwitches.js'));
const disabledFeatures = chromiumSwitches(false).find(arg => arg.startsWith('--disable-features='));
const url = 'https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi';
const label = process.argv[2] ?? 'agent-space-embedded';
const firstMessage = process.argv.includes('--first-message');
const result = { date: new Date().toISOString(), url, firstMessage };
const context = await chromium.launchPersistentContext('../validation/agent-browser-profile/space', {
executablePath: '/usr/bin/chromium', headless: true, viewport: { width: 1440, height: 1080 },
ignoreDefaultArgs: [disabledFeatures],
args: [disabledFeatures.replace('ThirdPartyStoragePartitioning,', ''), '--enable-unsafe-webgpu', '--use-angle=vulkan', '--enable-features=Vulkan', '--disable-vulkan-surface'],
});
context.setDefaultTimeout(240_000);
try {
const page = await context.newPage();
await page.goto(url, { waitUntil: 'domcontentloaded' });
console.log('Hugging Face page opened');
await page.waitForFunction(() => [...document.querySelectorAll('iframe')].some(frame => frame.src.includes('minicpm5-2b-webgpu-pi')));
// Hugging Face can replace its initial iframe while the parent initializes.
let app;
for (let attempt = 0; attempt < 4; attempt++) {
const element = await page.waitForSelector('iframe[src*="minicpm5-2b-webgpu-pi"]');
app = await element.contentFrame();
assert(app, 'The deployed Hugging Face app frame must exist.');
try {
await app.waitForFunction(() => window.browserPi?.terminal);
await app.evaluate(() => window.browserPi.ready);
break;
} catch (error) {
if (attempt === 3 || !/detached|context was destroyed/i.test(error.message)) throw error;
}
}
result.frameUrl = app.url();
result.cachedBeforeSend = await app.evaluate(() => window.browserPi.rpc('cache_status'));
console.log('App initialized');
const task = 'Read sales.csv, calculate total revenue using bash, and save a short report to report.md. Verify the file.';
if (firstMessage) {
await app.evaluate(() => document.querySelector('#new-chat').onclick());
await app.evaluate(() => window.browserPi.rpc('shell', { command: 'rm -f /workspace/report.md' }));
// Activate the iframe with a real click before sending keyboard events.
await app.locator('#pi-terminal').click();
await app.locator('.xterm-helper-textarea').focus(); await page.keyboard.type(task);
await app.waitForFunction(text => window.browserPi.terminal.text === text, task, { timeout: 15_000 });
console.log('First message typed');
await app.locator('#send').click();
if (!result.cachedBeforeSend) {
await app.waitForFunction(() => document.querySelector('#model-dialog').open);
console.log('First-message modal opened');
assert.equal(await app.locator('#model-preview').textContent(), task);
await page.screenshot({ path: `../validation/${label}-modal.png`, fullPage: true });
}
}
const start = Date.now();
if (!firstMessage || !result.cachedBeforeSend) await app.locator(firstMessage ? '#model-confirm' : '#load').click();
await app.waitForFunction(() => window.browserPi.loaded || (!window.browserPi.busy && (document.querySelector('#notice').textContent || !document.querySelector('#model-error').hidden)));
assert.equal(await app.evaluate(() => window.browserPi.loaded), true, await app.locator('#load-status').textContent());
result.loadMs = Date.now() - start;
result.device = await app.evaluate(() => window.browserPi.diagnostics.device);
if (!firstMessage) {
await app.waitForFunction(() => !window.browserPi.busy);
// Await the async UI handler: checking !busy immediately after a click can
// precede the worker's busy event and race the next setup command.
await app.evaluate(() => document.querySelector('#new-chat').onclick());
await app.evaluate(() => window.browserPi.rpc('shell', { command: 'rm -f /workspace/report.md' }));
await app.locator('.xterm-helper-textarea').focus();
await page.keyboard.type(task);
await app.locator('#send').click();
await app.waitForFunction(() => window.browserPi.busy);
await app.waitForFunction(() => !window.browserPi.busy);
} else {
await app.waitForFunction(() => window.browserPi.diagnostics.events.includes('agent_end') && !window.browserPi.busy);
assert.equal(await app.locator('#model-dialog').evaluate(el => el.open), false);
}
const state = await app.evaluate(() => window.browserPi.rpc('snapshot'));
result.messages = state.messages; result.report = state.files['/workspace/report.md'];
result.terminalScreen = await app.evaluate(() => window.browserPi.terminal.readScreen());
assert.equal(state.messages.filter(m => m.role === 'user').length, 1);
assert.match(result.report ?? '', /144/);
assert(state.messages.some(m => m.role === 'toolResult' && m.toolName === 'bash' && !m.isError));
assert(state.messages.filter(m => m.role === 'assistant').at(-1).content.some(c => c.type === 'text' && c.text.includes('144')));
await page.screenshot({ path: `../validation/${label}.png`, fullPage: true });
result.passed = true;
console.log('Embedded Hugging Face Space: real UI → Pi → WebGPU → bash → verified report passed.');
} catch (error) { result.failure = error.stack; throw error; }
finally { await fs.writeFile(`../validation/${label}.json`, JSON.stringify(result, null, 2)); await context.close(); }