/** * Проверка работы интерфейса в офлайне. * * Блокирует все запросы к внешним хостам: если оформление или иконки * подгружаются с CDN, они отвалятся. Успешное выполнение означает, что * страница самодостаточна и работает в контейнере без сети. * * Требуется запущенный сервер: * python -m uvicorn src.main:app --port 8123 * node tests/browser/ui_offline.js */ const fs = require('fs'); const path = require('path'); const os = require('os'); function resolvePlaywright() { if (process.env.PLAYWRIGHT) return process.env.PLAYWRIGHT; const base = path.join(os.homedir(), '.qwen', 'updates', 'npm'); if (!fs.existsSync(base)) return 'playwright-core'; const versions = fs.readdirSync(path.join(base, fs.readdirSync(base)[0], 'versions')).sort(); for (const v of versions.reverse()) { const candidate = path.join( base, fs.readdirSync(base)[0], 'versions', v, 'node_modules/@qwen-code/qwen-code/bundled/browser-use/runtime/node_modules/playwright-core' ); if (fs.existsSync(candidate)) return candidate; } return 'playwright-core'; } const { chromium } = require(resolvePlaywright()); const BASE = process.env.BASE_URL || 'http://127.0.0.1:8123'; const REPO = path.resolve(__dirname, '..', '..'); const FILE = path.join(REPO, 'dataset_hack/Для теста/spine_01.dcm'); (async () => { const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dxa-offline-')); const context = await chromium.launchPersistentContext(userDataDir, { channel: 'chrome', headless: true }); const page = context.pages()[0] || await context.newPage(); await page.setViewportSize({ width: 1400, height: 1100 }); const external = []; const failed = []; await context.route('**/*', (route) => { const url = route.request().url(); const isLocal = url.startsWith(BASE) || url.startsWith('data:') || url.startsWith('blob:'); if (!isLocal) { external.push(url); return route.abort(); } return route.continue(); }); page.on('requestfailed', (r) => failed.push(r.url())); await page.goto(BASE, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(1500); // Оформление применилось? Проверяем вычисленные стили, а не наличие файла. const styling = await page.evaluate(() => { const drop = document.getElementById('dropZone'); const cs = getComputedStyle(drop); const icon = document.querySelector('i.fas'); const iconFont = icon ? getComputedStyle(icon).fontFamily : null; const iconWidth = icon ? icon.getBoundingClientRect().width : 0; return { dropZoneBorderRadius: cs.borderRadius, dropZoneDisplay: cs.display, iconFontFamily: iconFont, iconWidth, }; }); console.log('styling:', styling); // Загрузка файла и открытие панели деталей в офлайне await page.setInputFiles('#fileInput', [FILE]); await page.waitForFunction(() => document.querySelectorAll('#resultsTable tr').length === 1, { timeout: 60000 }); await page.locator('#resultsTable tr').first().click(); await page.waitForTimeout(1200); const panel = await page.evaluate(() => ({ visible: !document.getElementById('detailSection').classList.contains('hidden'), basic: (document.getElementById('detailBasic') || {}).innerText?.replace(/\s+/g, ' ').trim(), reason: (document.getElementById('detailReason') || {}).innerText?.replace(/\s+/g, ' ').trim().slice(0, 70), images: document.querySelectorAll('#detailImage img, #detailMask img').length, })); console.log('panel:', panel); console.log('\nвнешние запросы (заблокированы):', external.length ? external : 'нет'); console.log('проваленные запросы:', failed.length ? failed : 'нет'); const ok = styling.dropZoneBorderRadius !== '0px' && panel.visible && panel.images === 2 && external.length === 0; console.log('\nОФЛАЙН-ПРОВЕРКА:', ok ? 'ПРОЙДЕНА' : 'НЕ ПРОЙДЕНА'); await context.close(); fs.rmSync(userDataDir, { recursive: true, force: true }); process.exit(ok ? 0 : 1); })().catch((e) => { console.error('FAILED:', e.message); process.exit(1); });