Download app/tests/load-flow.mjs from Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP: direct link, hf CLI and curl.
- Browser
- Download file 11.6 kB
-
https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/tests/load-flow.mjs
- Command line
-
hf download hf://spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/app/tests/load-flow.mjs
-
curl -L -o load-flow.mjs https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/tests/load-flow.mjs
11.6 kB
| import { chromium } from 'playwright'; | |
| import assert from 'node:assert/strict'; | |
| import fs from 'node:fs/promises'; | |
| const url = process.argv[2] ?? 'http://127.0.0.1:4173/'; | |
| const result = { url, fixture: 'Real app and workspace worker; controlled model load and prompt replies. GPU failure also tested without fixtures.', checks: [], errors: [] }; | |
| const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true, args: ['--disable-gpu', '--disable-software-rasterizer'] }); | |
| const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); | |
| page.setDefaultTimeout(15000); | |
| page.on('pageerror', error => result.errors.push(error.message)); | |
| // Control only the model boundary; initialization, files and the terminal are real. | |
| await page.addInitScript(() => { | |
| const NativeWorker = Worker; | |
| const fixture = window.loadFixture = { loads: [], prompts: [], cached: false }; | |
| window.Worker = class extends NativeWorker { | |
| constructor(...args) { super(...args); this.isAgent = String(args[0]).includes('agent.worker'); if (this.isAgent) fixture.worker = this; } | |
| postMessage(message, ...rest) { | |
| if (!this.isAgent) return super.postMessage(message, ...rest); | |
| const { id, action, args } = message; | |
| if (action === 'cache_status' && fixture.cached) { fixture.emit({ type: 'reply', id, value: true }); return; } | |
| if (action === 'load') { | |
| fixture.loads.push(args); fixture.pending = id; fixture.stopped = false; | |
| fixture.emit({ type: 'busy', action, cachedOnly: args.cachedOnly }); return; | |
| } | |
| if (action === 'stop' && fixture.pending) { | |
| fixture.stopped = true; fixture.emit({ type: 'reply', id, value: true }); return; | |
| } | |
| if (action === 'prompt') { | |
| fixture.prompts.push(args.text); | |
| const message = { role: 'user', content: [{ type: 'text', text: args.text }], timestamp: Date.now() }; | |
| fixture.emit({ type: 'busy', action }); | |
| fixture.emit({ type: 'agent_event', event: { type: 'message_start', message } }); | |
| fixture.emit({ type: 'reply', id, value: { messages: [message] } }); | |
| fixture.emit({ type: 'idle' }); return; | |
| } | |
| return super.postMessage(message, ...rest); | |
| } | |
| }; | |
| fixture.emit = data => fixture.worker.dispatchEvent(new MessageEvent('message', { data })); | |
| fixture.finish = (error, name = 'Error') => { | |
| const id = fixture.pending; fixture.pending = undefined; | |
| if (!error) fixture.emit({ type: 'loaded', device: { vendor: 'test fixture' } }); | |
| // Match the worker's error ordering: cleanup/idle before a rejected reply. | |
| if (error) fixture.emit({ type: 'idle' }); | |
| fixture.emit({ type: 'reply', id, value: {}, error, errorName: name }); | |
| if (!error) fixture.emit({ type: 'idle' }); | |
| }; | |
| }); | |
| const ready = async () => { await page.waitForFunction(() => window.browserPi?.terminal); await page.evaluate(() => window.browserPi.ready); }; | |
| const emit = data => page.evaluate(data => window.loadFixture.emit(data), data); | |
| const finish = (error, name) => page.evaluate(([error, name]) => window.loadFixture.finish(error, name), [error, name]); | |
| const draft = text => page.waitForFunction(text => !window.browserPi.busy && !document.querySelector('#model-dialog').open && window.browserPi.terminal.text === text, text); | |
| const send = async (text = '') => { | |
| await page.locator('.xterm-helper-textarea').focus(); | |
| if (text) await page.keyboard.type(text); | |
| await page.locator('#send').click(); | |
| await page.locator('#model-dialog[open]').waitFor(); | |
| }; | |
| const confirm = async () => { | |
| await page.locator('#model-confirm').click(); | |
| await page.waitForFunction(() => window.browserPi.busy && !document.querySelector('#model-dialog').open); | |
| }; | |
| try { | |
| await page.goto(url); await ready(); | |
| const text = "What's our total sales revenue?"; | |
| await send(text); await page.locator('#model-cancel').click(); await draft(text); | |
| assert.equal(await page.evaluate(() => window.loadFixture.loads.length), 0); | |
| await send(); await confirm(); | |
| assert(await page.locator('#progress').isVisible()); | |
| assert.equal(await page.locator('#progress').getAttribute('value'), null); | |
| assert.equal(await page.locator('#runtime-meter').getAttribute('data-state'), 'loading'); | |
| assert(await page.locator('#stop').isVisible()); | |
| for (const id of ['#send', '#new-chat', '#load']) assert(await page.locator(id).isDisabled()); | |
| await page.waitForFunction(text => window.browserPi.terminal.readScreen().includes(text) && window.browserPi.terminal.readScreen().includes('Queued'), text); | |
| assert(await page.locator('.xterm-helper-textarea').evaluate(el => el === document.activeElement)); | |
| await page.evaluate(() => { document.querySelector('#model-confirm').click(); window.browserPi.terminal.submit(); document.querySelector('#send').click(); }); | |
| assert.equal(await page.evaluate(() => window.loadFixture.loads.length), 1); | |
| assert.equal(await page.evaluate(() => window.loadFixture.prompts.length), 0); | |
| await emit({ type: 'load_progress', phase: 'download', loaded: 736000000, total: 1840000000 }); | |
| assert.equal(await page.locator('#progress').getAttribute('value'), '40'); | |
| assert.match(await page.locator('#load').getAttribute('title'), /Hugging Face/); | |
| for (const [width, height] of [[1440,1000],[851,800],[850,800],[390,844],[320,568]]) { | |
| await page.setViewportSize({ width, height }); | |
| await page.waitForTimeout(250); // Header capsule width transition. | |
| assert(await page.evaluate(() => { | |
| const header = document.querySelector('header').getBoundingClientRect(); | |
| return document.documentElement.scrollWidth <= innerWidth && [...document.querySelectorAll('#runtime-meter, .model-panel, #progress')].every(el => { | |
| const r = el.getBoundingClientRect(); return r.left >= header.left && r.right <= header.right && r.top >= header.top && r.bottom <= header.bottom; | |
| }); | |
| }), `Header overflow at ${width}`); | |
| if ([1440,390,320].includes(width)) await page.screenshot({ path: `../validation/load-flow-${width}.png`, fullPage: true }); | |
| } | |
| await emit({ type: 'load_progress', phase: 'compile' }); | |
| assert(await page.locator('#progress').isVisible()); assert.equal(await page.locator('#progress').getAttribute('value'), null); | |
| await emit({ type: 'load_progress', phase: 'warmup' }); | |
| assert.equal(await page.locator('#load').textContent(), 'Almost ready…'); | |
| await page.locator('#stop').click(); | |
| assert(await page.evaluate(() => window.loadFixture.stopped && window.browserPi.busy)); | |
| assert(await page.locator('#send').isDisabled()); | |
| await finish('Stopped.', 'AbortError'); await draft(text); | |
| assert(await page.locator('#progress').isHidden()); | |
| assert.equal(await page.evaluate(() => window.loadFixture.prompts.length), 0); | |
| result.checks.push('Confirmation closes immediately, queues the visible message, shows all header phases at five widths, rejects duplicate clicks; Stop waits for cleanup and restores the draft'); | |
| await page.setViewportSize({ width: 1440, height: 1000 }); | |
| await send(); await confirm(); await finish('Download interrupted. Please try again.'); | |
| await page.locator('#model-dialog[open]').waitFor(); | |
| assert.equal(await page.locator('#model-preview').textContent(), text); | |
| assert.match(await page.locator('#model-error').textContent(), /Download interrupted/); | |
| assert.equal(await page.locator('#model-confirm').textContent(), 'Try again'); | |
| assert(await page.locator('#progress').isHidden()); | |
| await page.keyboard.press('Escape'); await draft(text); | |
| await send(); await confirm(); await finish('Temporary connection failure.'); | |
| await page.locator('#model-dialog[open]').waitFor(); | |
| await confirm(); | |
| await page.locator('.xterm-helper-textarea').focus(); await page.keyboard.type('A follow-up draft'); | |
| await page.locator('#files button').first().focus(); | |
| await finish(); | |
| await page.waitForFunction(() => window.loadFixture.prompts.length === 1 && !window.browserPi.busy); | |
| assert.deepEqual(await page.evaluate(() => window.loadFixture.prompts), [text]); | |
| assert.equal(await page.evaluate(() => window.browserPi.terminal.text), 'A follow-up draft'); | |
| assert(await page.locator('#model-dialog').isHidden()); | |
| assert(await page.locator('#progress').isHidden()); | |
| assert(await page.locator('#files button').first().evaluate(el => el === document.activeElement)); | |
| await page.waitForFunction(() => !window.browserPi.terminal.readScreen().includes('Queued')); | |
| result.checks.push('Failure reopens retry with the original message; Escape keeps the draft; successful retry sends exactly once and retains a follow-up draft and the user’s current focus'); | |
| await page.reload(); await ready(); | |
| await page.evaluate(() => { window.loadFixture.cached = true; }); | |
| await page.locator('.xterm-helper-textarea').focus(); await page.keyboard.type('Cached message'); await page.locator('#send').click(); | |
| await page.waitForFunction(() => window.loadFixture.pending); | |
| assert(await page.locator('#model-dialog').isHidden()); | |
| assert.equal(await page.evaluate(() => window.loadFixture.loads[0].cachedOnly), true); | |
| await emit({ type: 'load_progress', phase: 'compile' }); | |
| assert(await page.locator('#progress').isVisible()); | |
| await page.keyboard.press('Escape'); await finish('Stopped.', 'AbortError'); await draft('Cached message'); | |
| result.checks.push('Cached restoration uses the same top bar without confirmation; Escape stops it without opening a retry dialog'); | |
| await page.reload(); await ready(); | |
| await page.locator('#load').click(); | |
| assert(await page.locator('#model-dialog').isHidden()); | |
| assert(await page.locator('#progress').isVisible()); | |
| await finish(); await page.waitForFunction(() => window.browserPi.loaded && !window.browserPi.busy); | |
| assert.equal(await page.evaluate(() => window.loadFixture.prompts.length), 0); | |
| result.checks.push('Loading from the header still works without queuing or sending a message'); | |
| // Real unsupported-GPU load, with no worker interception or fake progress. | |
| const actual = await browser.newPage({ viewport: { width: 390, height: 844 } }); | |
| actual.on('pageerror', error => result.errors.push(error.message)); | |
| await actual.goto(url); await actual.waitForFunction(() => window.browserPi?.terminal); await actual.evaluate(() => window.browserPi.ready); | |
| await actual.locator('.xterm-helper-textarea').focus(); await actual.keyboard.type('Keep my message'); await actual.locator('#send').click(); | |
| await actual.locator('#model-confirm').click(); | |
| await actual.locator('#model-error').waitFor({ state: 'visible' }); | |
| assert.match(await actual.locator('#model-error').textContent(), /WebGPU/); | |
| assert(await actual.locator('#progress').isHidden()); | |
| await actual.locator('#model-confirm').click(); await actual.locator('#model-error').waitFor({ state: 'visible' }); | |
| await actual.locator('#model-cancel').click(); | |
| await actual.waitForFunction(() => window.browserPi.terminal.text === 'Keep my message'); | |
| assert.equal((await actual.evaluate(() => window.browserPi.rpc('snapshot'))).messages.length, 0); | |
| await actual.close(); result.checks.push('Actual unsupported-WebGPU load retries and cancels safely with no message sent'); | |
| assert.deepEqual(result.errors, []); result.passed = true; | |
| } catch (error) { | |
| result.failure = error.stack; await page.screenshot({ path: '../validation/load-flow-failure.png', fullPage: true }); throw error; | |
| } finally { | |
| await fs.writeFile('../validation/load-flow.json', JSON.stringify(result, null, 2)); | |
| await browser.close(); console.log(JSON.stringify(result)); | |
| } | |