Download app/tests/terminal.mjs from Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP: direct link, hf CLI and curl.
- Browser
- Download file 7.33 kB
-
https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/tests/terminal.mjs
- Command line
-
hf download hf://spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/app/tests/terminal.mjs
-
curl -L -o terminal.mjs https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/tests/terminal.mjs
7.33 kB
| import { chromium } from 'playwright'; | |
| import fs from 'node:fs/promises'; | |
| import assert from 'node:assert/strict'; | |
| const url = process.argv[2] ?? process.env.APP_URL ?? 'http://127.0.0.1:4173/'; | |
| const label = process.argv[3] ?? process.env.TEST_LABEL ?? 'agent-terminal'; | |
| const result = { date: new Date().toISOString(), url, errors: [], requests: [], checks: [] }; | |
| const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true }); | |
| const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } }); | |
| context.setDefaultTimeout(15_000); | |
| const page = await context.newPage(); | |
| page.on('pageerror', error => { result.errors.push(error.stack); console.log('PAGEERROR', error.stack); }); | |
| page.on('console', message => { if (message.type() === 'error') console.log('CONSOLE', message.text().slice(0, 500)); }); | |
| context.on('request', request => result.requests.push(request.url())); | |
| const screen = () => page.evaluate(() => window.browserPi.terminal.readScreen()); | |
| const text = () => page.evaluate(() => window.browserPi.terminal.text); | |
| async function type(value) { await page.locator('.xterm-helper-textarea').focus(); await page.keyboard.type(value); } | |
| async function waitScreen(value) { await page.waitForFunction(value => window.browserPi.terminal.readScreen().includes(value), value); } | |
| try { | |
| await page.goto(url); | |
| await page.waitForFunction(() => window.browserPi); | |
| await page.evaluate(() => window.browserPi.ready); | |
| await waitScreen('browser workspace'); | |
| result.initial = await screen(); | |
| assert(result.initial.includes('/load')); | |
| result.checks.push('Original Pi TUI rendered through xterm'); | |
| await type('/help'); await page.keyboard.press('Enter'); await waitScreen('Ctrl+O'); | |
| result.checks.push('Enter submits /help without loading a model'); | |
| await page.keyboard.press('Control+Shift+f'); await type('browser'); | |
| await page.waitForFunction(() => window.browserPi.terminal.text === '' && /\d+\/\d+/.test(window.browserPi.terminal.readScreen())); | |
| await page.keyboard.press('Escape'); await page.waitForTimeout(30); | |
| result.checks.push('Pi fullscreen transcript search preserves the editor draft'); | |
| await type('one'); await page.keyboard.press('Shift+Enter'); await type('two'); | |
| assert.equal(await text(), 'one\ntwo'); | |
| await page.keyboard.press('Backspace'); await page.keyboard.press('Control+a'); await type('X'); | |
| assert.equal(await text(), 'one\nXtw'); | |
| await page.keyboard.press('Control+u'); await page.keyboard.press('Control+k'); | |
| await page.keyboard.press('Backspace'); await page.keyboard.press('Control+u'); | |
| assert.equal(await text(), ''); | |
| result.checks.push('Pi multiline editor: Shift+Enter, cursor, deletion, Ctrl+U/K'); | |
| await page.keyboard.press('Control+p'); assert.equal(await text(), '/help'); | |
| await page.keyboard.press('Control+n'); assert.equal(await text(), ''); | |
| result.checks.push('Pi prompt history'); | |
| await type('/he'); await page.keyboard.press('Tab'); | |
| await page.waitForFunction(() => window.browserPi.terminal.text === '/help '); | |
| await page.keyboard.press('Enter'); await waitScreen('Ctrl+O'); | |
| result.checks.push('Pi autocomplete: /he → /help'); | |
| await type("!printf 'terminal-works\\n' > terminal.txt; cat terminal.txt"); | |
| await page.keyboard.press('Escape'); // Dismiss optional workspace completion. | |
| await page.waitForTimeout(30); // A lone ESC is delimited by Pi's 10ms input timer. | |
| await page.keyboard.press('Enter'); await waitScreen('[exit 0]'); | |
| await page.waitForFunction(() => !window.browserPi.busy); | |
| const state = await page.evaluate(() => window.browserPi.rpc('snapshot')); | |
| assert.equal(state.files['/workspace/terminal.txt'], 'terminal-works\n'); | |
| assert((await screen()).includes('[exit 0]')); | |
| result.checks.push('xterm keyboard → Pi editor → real just-bash → workspace file'); | |
| // Model/file text must not be able to clear the terminal or inject OSC URLs. | |
| await type("!printf 'before\\033[2Jafter\\n'"); | |
| await page.locator('#send').click(); await waitScreen('beforeafter'); | |
| result.checks.push('Shell escape sequences render as inert text'); | |
| // Paste uses xterm's actual bracketed-paste path through Pi's StdinBuffer. | |
| await context.grantPermissions(['clipboard-read', 'clipboard-write']); | |
| const largePaste = '!cat > pasted.txt <<\'EOF\'\n' + Array.from({ length: 15 }, (_, i) => 'row-' + i).join('\n') + '\nEOF'; | |
| await page.evaluate(text => navigator.clipboard.writeText(text), largePaste); | |
| await page.locator('.xterm-helper-textarea').focus(); await page.keyboard.press('Control+v'); | |
| await page.waitForFunction(text => window.browserPi.terminal.text === text, largePaste); | |
| await page.locator('#send').click(); | |
| await page.waitForFunction(() => document.querySelector('#files').textContent.includes('pasted.txt')); | |
| const pastedState = await page.evaluate(() => window.browserPi.rpc('snapshot')); | |
| assert.equal(pastedState.files['/workspace/pasted.txt'], Array.from({ length: 15 }, (_, i) => 'row-' + i).join('\n') + '\n'); | |
| result.checks.push('Large Pi paste markers expand before Send'); | |
| const pasted = 'hello 世界 🦙\nline two'; | |
| await page.evaluate(text => navigator.clipboard.writeText(text), pasted); | |
| await page.locator('.xterm-helper-textarea').focus(); await page.keyboard.press('Control+v'); | |
| await page.waitForFunction(text => window.browserPi.terminal.text === text, pasted); | |
| result.checks.push('Unicode multiline clipboard paste'); | |
| await page.locator('#newline').click(); assert.equal(await text(), pasted + '\n'); | |
| result.checks.push('Touch-accessible newline button'); | |
| // Resize while preserving a multiline draft and Pi's own viewport renderer. | |
| const desktop = await page.evaluate(() => ({ cols: window.browserPi.terminal.cols, rows: window.browserPi.terminal.rows })); | |
| await page.screenshot({ path: `../validation/${label}-desktop.png`, fullPage: true }); | |
| await page.setViewportSize({ width: 390, height: 844 }); | |
| await page.waitForFunction(cols => window.browserPi.terminal.cols < cols, desktop.cols); | |
| assert.equal(await text(), pasted + '\n'); | |
| assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)); | |
| result.sizes = { desktop, mobile: await page.evaluate(() => ({ cols: window.browserPi.terminal.cols, rows: window.browserPi.terminal.rows })) }; | |
| assert(result.sizes.mobile.cols >= 30); assert(result.sizes.mobile.rows >= 15); | |
| await page.screenshot({ path: `../validation/${label}-mobile.png`, fullPage: true }); | |
| result.checks.push('390px layout and live terminal resize preserve the draft'); | |
| result.mobileLimitation = 'Desktop Chromium viewport simulation; physical mobile keyboards and GPUs not tested.'; | |
| assert.equal(result.errors.length, 0); | |
| assert.equal(result.requests.filter(url => /^https?:/.test(url) && new URL(url).origin !== new URL(result.url).origin).length, 0); | |
| result.passed = true; console.log(JSON.stringify({ passed: true, checks: result.checks, sizes: result.sizes })); | |
| } catch (error) { | |
| result.failure = error.stack; | |
| result.screen = await screen().catch(() => 'App failed before initialization'); | |
| await page.screenshot({ path: `../validation/${label}-failure.png`, fullPage: true }); | |
| throw error; | |
| } finally { await fs.writeFile(`../validation/${label}.json`, JSON.stringify(result, null, 2)); await browser.close(); } | |