macos-web/tests/helpers.js
2026-07-17 16:52:13 +08:00

100 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 测试公共助手:静态服务器 / 浏览器 / 启动解锁 / 错误收集
* 仅测试代码依赖 Node 与 Playwright生产页面不依赖 Node。 */
'use strict';
const http = require('http');
const fs = require('fs');
const path = require('path');
const { chromium } = require('/home/kimi/.npm-global/lib/node_modules/playwright');
const ROOT = path.resolve(__dirname, '..');
const FILE_URL = 'file://' + path.join(ROOT, 'index.html');
const MIME = {
'.html': 'text/html; charset=utf-8', '.css': 'text/css; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.svg': 'image/svg+xml',
'.mp3': 'audio/mpeg', '.mp4': 'video/mp4', '.webm': 'video/webm', '.json': 'application/json', '.md': 'text/markdown; charset=utf-8',
};
/* 极简静态文件服务器Node 原生实现,避免依赖 python端口占用时自动递增重试 */
function serveStatic(port = 8177) {
const tryListen = p => new Promise((resolve, reject) => {
const server = http.createServer((req, res) => {
try {
let u = decodeURIComponent(req.url.split('?')[0]);
if (u === '/') u = '/index.html';
const file = path.normalize(path.join(ROOT, u));
if (!file.startsWith(ROOT)) { res.writeHead(403); return res.end('forbidden'); }
if (!fs.existsSync(file) || fs.statSync(file).isDirectory()) { res.writeHead(404); return res.end('not found: ' + u); }
res.writeHead(200, { 'Content-Type': MIME[path.extname(file).toLowerCase()] || 'application/octet-stream' });
fs.createReadStream(file).pipe(res);
} catch (e) { res.writeHead(500); res.end(String(e)); }
});
server.once('error', e => { if (e.code === 'EADDRINUSE' && p < 8200) resolve(tryListen(p + 1)); else reject(e); });
server.listen(p, '127.0.0.1', () => resolve({
url: `http://127.0.0.1:${p}/index.html`, port: p, close: () => new Promise(r => server.close(r)),
}));
});
return tryListen(port);
}
let _browser = null;
async function browser() {
if (!_browser) _browser = await chromium.launch({
// 沙箱环境 /dev/shm 仅 64M需改用 /tmp 避免 renderer 崩溃
args: ['--no-sandbox', '--disable-dev-shm-usage'],
});
return _browser;
}
async function closeBrowser() { if (_browser) { await _browser.close(); _browser = null; } }
/* 打开一个全新上下文页面,并收集未捕获异常与 console.error */
async function newPage(url, { viewport = { width: 1440, height: 900 } } = {}) {
const b = await browser();
const ctx = await b.newContext({ viewport });
const page = await ctx.newPage();
const errors = [];
page.on('pageerror', e => errors.push('[pageerror] ' + e.message));
page.on('console', msg => {
if (msg.type() === 'error') {
const t = msg.text();
// 忽略 favicon/资源 404 的网络层噪音(另有资源完整性专项测试)
if (/Failed to load resource/i.test(t)) return;
errors.push('[console.error] ' + t);
}
});
await page.goto(url);
return { ctx, page, errors };
}
/* 等待开机完成并进入锁屏,然后解锁进入桌面 */
async function bootToLock(page) {
await page.waitForSelector('#lockscreen:not(.hidden)', { timeout: 12000 });
}
async function unlock(page) {
await bootToLock(page);
await page.click('#lockscreen');
await page.waitForSelector('#desktop:not(.hidden)', { timeout: 8000 });
await page.waitForTimeout(200);
}
/* 在页面上下文打开应用并等待窗口出现 */
async function openApp(page, appId, args) {
await page.evaluate(([id, a]) => window.Apps.open(id, a), [appId, args || null]);
await page.waitForSelector(`.window`, { timeout: 5000 });
await page.waitForTimeout(120);
}
/* 关闭当前活动窗口(点红灯) */
async function closeActiveWindow(page) {
await page.evaluate(() => { const w = window.WM.activeWin; if (w) window.WM.close(w); });
}
/* 读取窗口状态 */
const winCount = page => page.evaluate(() => window.WM.windows.length);
const winTitles = page => page.evaluate(() => window.WM.windows.map(w => w.appId + ':' + w.title));
module.exports = {
ROOT, FILE_URL, serveStatic, browser, closeBrowser, newPage,
bootToLock, unlock, openApp, closeActiveWindow, winCount, winTitles,
};