Download app/tests/editor.mjs from Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP: direct link, hf CLI and curl.
- Browser
- Download file 7.86 kB
-
https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/tests/editor.mjs
- Command line
-
hf download hf://spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/app/tests/editor.mjs
-
curl -L -o editor.mjs https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/tests/editor.mjs
7.86 kB
| 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/'; | |
| const label = process.argv[3] ?? 'agent-editor'; | |
| const result = { url, checks: [], errors: [] }; | |
| const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true }); | |
| const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); | |
| page.on('pageerror', error => result.errors.push(error.message)); | |
| const editor = page.locator('#editor'), layer = page.locator('#editor-highlight'); | |
| async function write(name, content) { | |
| await page.evaluate(({ name, content }) => window.browserPi.rpc('write', { path: '/workspace/' + name, content }), { name, content }); | |
| } | |
| async function select(name) { | |
| await page.locator(`#files button[title="/workspace/${name}"]`).click(); | |
| } | |
| async function highlighted(text) { | |
| await page.waitForFunction(text => document.querySelector('.editor-input').classList.contains('is-highlighted') && document.querySelector('#editor-highlight').textContent === text + '\n', text); | |
| } | |
| try { | |
| await page.goto(url); await page.waitForFunction(() => window.browserPi); | |
| await page.evaluate(() => window.browserPi.ready); | |
| const html = '<!DOCTYPE html>\n<html lang="en">\n<head>\n <style>body { background: #18181e; color: white; }</style>\n</head>\n<body>\n <!-- A tiny browser game -->\n <canvas id="game" width="600" height="600"></canvas>\n <script>\n const canvas = document.getElementById("game");\n function draw(score) {\n return score + 1;\n }\n </script>\n</body>\n</html>\n'; | |
| const fixtures = { | |
| 'snake-game.html': [html, '.token.tag, .token.keyword, .token.selector'], | |
| 'app.js': ['// café 🐈\nconst total = items.reduce((sum, item) => sum + item.price, 0);\n', '.token.keyword'], | |
| 'view.tsx': ['const View = (props: { label: string }) => <button>{props.label}</button>;\n', '.token.tag'], | |
| 'script.py': ['# Sales\ndef total(rows):\n\treturn sum(row["price"] for row in rows)\n', '.token.keyword'], | |
| 'style.css': ['body { color: #fff; margin: 0; }\n', '.token.property'], | |
| 'data.json': ['{"total": 274.88, "verified": true}\n', '.token.property'], | |
| 'notes.md': ['# Report\n\n**Total:** `274.88`\n', '.token.title'], | |
| 'config.yaml': ['version: 1\nfiles:\n - sales.csv\n', '.token.key'], | |
| 'query.sql': ['SELECT SUM(units * price) AS total FROM sales;\n', '.token.keyword'], | |
| 'run.sh': ['#!/bin/bash\nfor file in *.csv; do echo "$file"; done\n', '.token.keyword'], | |
| }; | |
| for (const [name, [content, token]] of Object.entries(fixtures)) { | |
| await write(name, content); await select(name); await highlighted(content); | |
| assert(await layer.locator(token).count(), name); | |
| assert.equal(await editor.inputValue(), content); | |
| } | |
| result.checks.push('Common language grammars render colors and preserve exact source, including Unicode, tabs and trailing newlines'); | |
| await select('snake-game.html'); await highlighted(html); | |
| for (const token of ['.token.tag', '.token.keyword', '.token.selector']) assert(await layer.locator(token).count()); | |
| assert.equal(await layer.locator('script, style, canvas, img, iframe').count(), 0); | |
| await page.screenshot({ path: `../validation/${label}-html.png`, fullPage: true }); | |
| // Real keyboard edits must retain selection, undo and the file-saving flow. | |
| await editor.focus(); await page.keyboard.press('Control+End'); | |
| await page.keyboard.insertText('<!-- edited -->'); await highlighted(html + '<!-- edited -->'); | |
| await page.keyboard.press('Control+z'); await highlighted(html); | |
| await page.keyboard.type('<!-- saved -->'); await highlighted(html + '<!-- saved -->'); | |
| await page.locator('#save').click(); await page.waitForFunction(() => document.querySelector('#save').disabled); | |
| assert.equal(await page.evaluate(() => window.browserPi.rpc('snapshot').then(state => state.files['/workspace/snake-game.html'])), html + '<!-- saved -->'); | |
| await editor.fill('<img src=x onerror="window.sourceExecuted=true"><script>window.sourceExecuted=true</script>'); | |
| await highlighted(await editor.inputValue()); | |
| assert.equal(await page.evaluate(() => window.sourceExecuted), undefined); | |
| assert.equal(await layer.locator('img, script').count(), 0); | |
| await editor.fill(html); await highlighted(html); await page.locator('#save').click(); | |
| await page.waitForFunction(() => document.querySelector('#save').disabled); | |
| result.checks.push('Native keyboard editing and undo work; Save persists exact content; HTML source remains inert'); | |
| const long = Array.from({ length: 500 }, (_, i) => `const row${i} = "${'x'.repeat(120)}";`).join('\n') + '\n'; | |
| await write('long.js', long); await select('long.js'); await highlighted(long); | |
| for (const [width, height] of [[1440,1000], [390,844], [320,568]]) { | |
| await page.setViewportSize({ width, height }); await editor.scrollIntoViewIfNeeded(); | |
| await editor.evaluate(el => { el.scrollTop = el.scrollHeight; el.scrollLeft = el.scrollWidth; }); | |
| await page.waitForFunction(() => { | |
| const input = document.querySelector('#editor'), pre = document.querySelector('#editor-highlight'); | |
| return pre.scrollTop === input.scrollTop && pre.scrollLeft === input.scrollLeft && pre.clientWidth === input.clientWidth && pre.clientHeight === input.clientHeight; | |
| }); | |
| assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)); | |
| const fonts = await page.evaluate(() => ['#editor', '#editor-highlight'].map(selector => { | |
| const style = getComputedStyle(document.querySelector(selector)); | |
| return [style.font, style.padding, style.tabSize, style.whiteSpace, style.letterSpacing]; | |
| })); | |
| assert.deepEqual(fonts[0], fonts[1]); | |
| } | |
| await select('snake-game.html'); await highlighted(html); await editor.scrollIntoViewIfNeeded(); | |
| assert.deepEqual(await editor.evaluate(el => [el.scrollTop, el.scrollLeft]), [0, 0]); | |
| await page.screenshot({ path: `../validation/${label}-mobile.png`, fullPage: true }); | |
| result.checks.push('Highlighted text stays aligned with the caret and both scroll axes on desktop and narrow mobile layouts'); | |
| // Rapid switching cannot let an old worker response overwrite the new file. | |
| await select('long.js'); await select('data.json'); await highlighted(fixtures['data.json'][0]); | |
| await write('plain.txt', '<b>plain text</b>'); await select('plain.txt'); | |
| assert.equal(await page.locator('.is-highlighted').count(), 0); | |
| const huge = '// large file\n' + 'x'.repeat(250_000); | |
| await write('large.js', huge); await select('large.js'); | |
| assert.equal(await page.locator('.is-highlighted').count(), 0); | |
| const started = Date.now(); | |
| await editor.focus(); await page.keyboard.press('Control+End'); await page.keyboard.type(' edited'); | |
| assert((await editor.inputValue()).endsWith(' edited')); | |
| result.largeFileEditMs = Date.now() - started; | |
| await page.locator('#save').click(); await page.waitForFunction(() => document.querySelector('#save').disabled); | |
| await select('snake-game.html'); await highlighted(html); | |
| await page.emulateMedia({ forcedColors: 'active' }); | |
| assert.equal(await layer.evaluate(el => getComputedStyle(el).visibility), 'hidden'); | |
| assert.notEqual(await editor.evaluate(el => getComputedStyle(el).color), 'rgba(0, 0, 0, 0)'); | |
| result.checks.push('Rapid file switching avoids stale colors; plain/large files remain editable; forced-colors mode shows native text'); | |
| assert.deepEqual(result.errors, []); result.passed = true; | |
| console.log(JSON.stringify(result, null, 2)); | |
| } catch (error) { | |
| result.failure = error.stack; | |
| 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(); | |
| } | |