Download app/tests/modal-cat.mjs from Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP: direct link, hf CLI and curl.
- Browser
- Download file 9.8 kB
-
https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/tests/modal-cat.mjs
- Command line
-
hf download hf://spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/app/tests/modal-cat.mjs
-
curl -L -o modal-cat.mjs https://huggingface.co/spaces/Mike0021/MiniCPM5-2B-WebGPU-Pi-HTTP/resolve/main/app/tests/modal-cat.mjs
9.8 kB
| import { chromium, webkit } 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-modal-cat'; | |
| const result = { url, checks: [], layouts: [], errors: [] }; | |
| const webkitRoot = process.env.WEBKIT_MINIBROWSER_ROOT; | |
| for (const engine of ['chromium', ...(process.env.TEST_WEBKIT ? ['webkit'] : [])]) { | |
| const browser = engine === 'chromium' | |
| ? await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true, args: ['--disable-gpu', '--disable-software-rasterizer'] }) | |
| : await webkit.launch({ headless: true, ...(webkitRoot ? { executablePath: webkitRoot + '/bin/MiniBrowser', env: { ...process.env, | |
| WEBKIT_EXEC_PATH: webkitRoot + '/bin', WEBKIT_INJECTED_BUNDLE_PATH: webkitRoot + '/lib', WEBKIT_INSPECTOR_RESOURCES_PATH: webkitRoot + '/share', | |
| LD_LIBRARY_PATH: webkitRoot + '/lib:' + webkitRoot + '/sys/lib:' + (process.env.WEBKIT_EXTRA_LIBS ?? '') } } : {}) }); | |
| const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); | |
| const requests = []; | |
| const catRequests = []; | |
| page.on('request', r => { | |
| requests.push(r.url()); | |
| if (r.url().includes('modal-cat') && !r.redirectedFrom()) catRequests.push(r.url()); | |
| }); | |
| page.on('pageerror', e => result.errors.push(e.message)); | |
| let releaseImage; | |
| try { | |
| const imageGate = new Promise(resolve => { releaseImage = resolve; }); | |
| await page.route('**/*modal-cat*.png', async route => { await imageGate; await route.continue(); }); | |
| await page.goto(url, { waitUntil: 'domcontentloaded' }); await page.waitForFunction(() => window.browserPi?.terminal); await page.evaluate(() => window.browserPi.ready); | |
| assert(requests.some(u => u.includes('modal-cat')), 'The sprite must start loading before any interaction'); | |
| assert(await page.locator('link[rel="preload"][as="image"]').evaluate(link => link.href === document.querySelector('.setup-cat img').src)); | |
| const prompt = "What's our total sales revenue?"; | |
| await page.locator('.xterm-helper-textarea').focus(); await page.keyboard.type(prompt); await page.locator('#send').click(); | |
| await page.locator('#model-dialog[open]').waitFor(); | |
| assert.equal(await page.locator('.setup-cat img').evaluate(el => getComputedStyle(el).opacity), '0'); | |
| assert(await page.locator('#model-confirm').evaluate(el => document.activeElement === el)); | |
| await page.locator('#model-cancel').focus(); await page.keyboard.press('Enter'); | |
| assert.equal(await page.evaluate(() => window.browserPi.terminal.text), prompt); | |
| releaseImage(); | |
| await page.waitForFunction(() => document.querySelector('.setup-cat').classList.contains('is-ready')); | |
| assert(await page.locator('#model-dialog').isHidden()); | |
| assert.equal(await page.locator('.setup-cat-tail').evaluate(el => el.getAnimations().length), 0); | |
| await page.evaluate(() => { | |
| const dialog = document.querySelector('#model-dialog'); | |
| const observer = new MutationObserver(() => { | |
| if (!dialog.open) return; | |
| const img = dialog.querySelector('.setup-cat img'); | |
| window.catAtModalOpen = img.complete && img.naturalWidth > 0 && getComputedStyle(img).opacity === '1'; | |
| observer.disconnect(); | |
| }); | |
| observer.observe(dialog, { attributes: true, attributeFilter: ['open'] }); | |
| }); | |
| await page.locator('#send').click(); | |
| await page.locator('#model-dialog[open]').waitFor(); | |
| assert.equal(await page.evaluate(() => window.catAtModalOpen), true, 'The decoded cat must be visible at modal opening'); | |
| assert.equal(await page.locator('#model-cancel').getAttribute('aria-label'), 'Close model setup'); | |
| assert.equal(await page.locator('#model-cancel svg').count(), 1); | |
| await page.keyboard.press('Shift+Tab'); | |
| assert(await page.locator('.setup-credits a:last-child').evaluate(el => document.activeElement === el)); | |
| await page.keyboard.press('Tab'); | |
| assert(await page.locator('#model-confirm').evaluate(el => document.activeElement === el)); | |
| assert.equal(catRequests.length, 1, 'Preload and image must reuse the same request'); | |
| assert.equal(await page.locator('.setup-cat img').getAttribute('alt'), ''); | |
| assert.equal(await page.locator('.setup-cat').getAttribute('aria-hidden'), 'true'); | |
| // Spaces may redirect assets to a CDN, which taints a DOM image's canvas. | |
| // Inspect the same response bytes without changing the displayed sprite. | |
| const sprite = await page.request.get(await page.locator('.setup-cat img').evaluate(img => img.currentSrc)); | |
| assert(sprite.ok()); | |
| const alpha = await page.evaluate(async source => { | |
| const img = new Image(); img.src = source; await img.decode(); | |
| const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; | |
| const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); | |
| return [[0,0],[1253,0],[0,1253],[1253,1253]].map(([x,y]) => ctx.getImageData(x,y,1,1).data[3]); | |
| }, 'data:image/png;base64,' + (await sprite.body()).toString('base64')); | |
| assert.deepEqual(alpha, [0,0,0,0]); | |
| result.checks.push(engine + ': sprite preloads once at page startup and decodes while closed; visible at modal opening; slow image does not block Cancel; transparent PNG adds no focus stop'); | |
| const tailFrames = await page.locator('.setup-cat-tail').evaluate(el => { | |
| const animation = el.getAnimations()[0]; animation.pause(); | |
| return [0, 2970, 3690, 4500].map(time => { | |
| animation.currentTime = time; | |
| return { transform: getComputedStyle(el).transform, body: getComputedStyle(el.nextElementSibling).transform }; | |
| }); | |
| }); | |
| assert(tailFrames.every(frame => frame.body === 'none')); | |
| assert.notEqual(tailFrames[0].transform, tailFrames[1].transform); | |
| assert.notEqual(tailFrames[1].transform, tailFrames[2].transform); | |
| assert.equal(tailFrames[0].transform, tailFrames[3].transform); | |
| await page.emulateMedia({ reducedMotion: 'reduce' }); | |
| assert.equal(await page.locator('.setup-cat-tail').evaluate(el => el.getAnimations().length), 0); | |
| await page.emulateMedia({ reducedMotion: 'no-preference' }); | |
| result.checks.push(engine + ': only the tail swishes and settles; animation is absent while closed or with reduced motion'); | |
| for (const [width, height] of [[1440,1000],[1024,768],[850,640],[390,844],[375,667],[320,568],[390,420],[390,320]]) { | |
| await page.setViewportSize({ width, height }); | |
| const layout = await page.evaluate(() => { | |
| const rect = s => document.querySelector(s).getBoundingClientRect().toJSON(); | |
| return { dialog: rect('#model-dialog'), cat: rect('.setup-cat'), title: rect('#model-title'), actions: rect('.setup-actions'), confirm: rect('#model-confirm'), close: rect('#model-cancel'), body: rect('.setup-body'), facts: rect('.setup-facts'), | |
| overflow: document.documentElement.scrollWidth > innerWidth, | |
| blend: getComputedStyle(document.querySelector('.setup-cat img')).mixBlendMode }; | |
| }); | |
| assert(!layout.overflow); | |
| assert(layout.cat.top >= 8 && layout.cat.right <= width - 8, JSON.stringify(layout)); | |
| assert(layout.dialog.bottom <= height - 8 && layout.dialog.left >= 8, JSON.stringify(layout)); | |
| assert(layout.actions.bottom <= height - 8 && layout.actions.top >= layout.dialog.top, JSON.stringify(layout)); | |
| assert(layout.cat.bottom <= layout.title.top + 1, JSON.stringify(layout)); | |
| assert(Math.abs(layout.confirm.width - layout.body.width) < 1, 'Primary action should fill the modal content width'); | |
| assert(layout.close.right <= layout.dialog.right && layout.close.bottom <= layout.body.top && layout.close.left >= layout.title.right, JSON.stringify(layout)); | |
| if (height >= 568) assert(layout.facts.bottom <= layout.body.bottom + 1, 'The full download note should fit: ' + JSON.stringify(layout)); | |
| assert.equal(layout.blend, 'normal'); | |
| await page.screenshot({ path: `../validation/${label}-${engine}-${width}x${height}.png` }); | |
| result.layouts.push({ engine, width, height, ...layout }); | |
| } | |
| await page.setViewportSize({ width: 390, height: 420 }); | |
| await page.locator('#model-confirm').click(); | |
| await page.locator('#model-error').waitFor({ state: 'visible' }); | |
| assert.match(await page.locator('#model-error').textContent(), /WebGPU/); | |
| assert.equal(await page.locator('#model-confirm').textContent(), 'Try again'); | |
| assert.equal(await page.locator('#model-cancel svg').count(), 1); | |
| assert(await page.locator('#model-cancel').isVisible()); | |
| assert(await page.locator('.setup-cat.is-ready').isVisible()); | |
| await page.screenshot({ path: `../validation/${label}-${engine}-error.png` }); | |
| await page.locator('#model-cancel').click(); | |
| assert.equal(await page.locator('.setup-cat-tail').evaluate(el => el.getAnimations().length), 0); | |
| assert.equal(await page.evaluate(() => window.browserPi.terminal.text), prompt); | |
| assert(!requests.some(u => /\/resolve\/|\/models\//.test(u))); | |
| result.checks.push(engine + ': eight viewport sizes keep the cat and actions in bounds; real GPU failure keeps retry and cancel usable'); | |
| } catch (e) { | |
| result.failure = e.stack; process.exitCode = 1; | |
| await page.screenshot({ path: `../validation/${label}-${engine}-failure.png` }).catch(() => {}); | |
| } finally { | |
| releaseImage?.(); await browser.close(); | |
| await fs.writeFile(`../validation/${label}.json`, JSON.stringify(result, null, 2)); | |
| } | |
| } | |
| result.passed = !result.failure && !result.errors.length; | |
| await fs.writeFile(`../validation/${label}.json`, JSON.stringify(result, null, 2)); | |
| console.log(JSON.stringify({ passed: result.passed, checks: result.checks, failure: result.failure })); | |