'use strict'; // Native Chromium + complete production header and app. Only startup/rendering // I/O and external account/server responses are replaced in memory. const fs = require('node:fs'), path = require('node:path'), assert = require('node:assert/strict'); const root = process.argv[3] || path.join(__dirname, '..'); const workspace = path.resolve(__dirname, '../../..'); let playwright; for (const candidate of [process.env.HOCKEY_TEST_PLAYWRIGHT, 'playwright', process.env.CODEX_PRIMARY_RUNTIME_NODE_MODULES && path.join(process.env.CODEX_PRIMARY_RUNTIME_NODE_MODULES, 'playwright'), '/opt/codex/runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright'].filter(Boolean)) { try { playwright = require(candidate); break; } catch (_) {} } assert.ok(playwright, 'Playwright is required'); const asset = name => fs.readFileSync(path.join(root, name), 'utf8'); const originalApp = asset('ui_builder/static/app.js'); const bootstrap = ' init();\n})();'; assert.equal(originalApp.split(bootstrap).length, 2, 'Expected exactly one production bootstrap'); const app = originalApp.replace(bootstrap, ` // Rendering the full roster/canvas is I/O outside this header integration. renderRuntime = () => {}; window.__auxHeader = { state, bindEvents, shortcutReferenceItems }; })();`); let browser; async function fixture({ holdFlags = false } = {}) { const chromium = process.env.HOCKEY_TEST_CHROMIUM || path.join(workspace, 'audit/browser_test/chromium'); browser = await playwright.chromium.launch({ headless: true, executablePath: chromium, env: { ...process.env, LD_LIBRARY_PATH: path.join(workspace, 'audit/browser_test/libs') }, args: ['--no-sandbox', '--no-zygote', '--disable-dev-shm-usage', '--disable-gpu', '--use-gl=disabled'] }); const context = await browser.newContext({ viewport: { width: 1150, height: 780 } }); const unexpected = [], errors = []; await context.route('**/*', route => { if (route.request().url() === 'http://aux-header-199.invalid/') return route.fulfill({ contentType: 'text/html', body: '
' }); unexpected.push(route.request().url()); return route.abort(); }); const page = await context.newPage(); page.setDefaultTimeout(6000); page.on('pageerror', error => errors.push(String(error))); await page.goto('http://aux-header-199.invalid/'); for (const name of ['ui_builder/static/styles.css', 'hockey_data/static/match-status-header.css']) await page.addStyleTag({ content: asset(name) }); await page.evaluate(({ holdFlags }) => { window.UI_BUILDER_BOOT = { mode: 'runtime', editorHotkey: 'Ctrl+Shift+E' }; window.__rights = true; window.__accessGeneration = 1; window.__flagRequests = []; window.__api = []; window.__unexpectedApi = []; window.__holdFlags = holdFlags; window.__heldFlags = []; window.__legacyF2 = 0; window.__openedTimers = 0; localStorage.setItem('hockey.accountId', 'operator199'); localStorage.setItem('hockey.operatorSessionToken', 'session199'); localStorage.setItem('hockey.vmix.selected_device', 'agent199'); window.HockeyAgentRuntime = { currentDeviceId: () => 'agent199' }; window.HockeyMatchAccess = { storage: localStorage, canControl: () => window.__rights, snapshot: () => ({ generation: window.__accessGeneration, participant_id: 'participant199' }), paint() {}, isPeriodPending: () => false, isWritePending: () => false, }; window.__control = { game_id: 'g', revision: 1, current_period: '1', period_options: [{ value: '1', label: '1 период' }], flags: {}, values: {}, score: { home: 0, away: 0, manual: true }, strength: {}, timers: {}, timer_rules: { minutes: 20 }, scoreboard_team_states: {} }; window.fetch = async (input, init = {}) => { const url = new URL(String(input), location.href), body = init.body ? JSON.parse(init.body) : null; window.__api.push({ path: url.pathname, method: init.method || 'GET', body }); const response = value => new Response(JSON.stringify(value), { headers: { 'Content-Type': 'application/json' } }); if (url.pathname === '/api/hockey/games/g/control/flags') { window.__flagRequests.push(body); if (window.__holdFlags) await new Promise(resolve => window.__heldFlags.push(resolve)); Object.assign(window.__control.flags, body.flags); window.__control.revision++; return response(structuredClone(window.__control)); } if (url.pathname === '/api/hockey/games/g/control') return response(structuredClone(window.__control)); if (url.pathname === '/api/hockey/games/g/control/timers') return response(structuredClone(window.__control)); if (url.pathname === '/api/hockey/vmix/sequence') return response({ ok: true, confirmation: 'ack', applied: body.commands.length, results: body.commands.map(command => ({ ok: true, function: command.Function, ack_waited: true })) }); if (/\/apply-mapping$/.test(url.pathname)) return response({ ok: true, complete: true, mapping_ack_waited: true, total_commands: 1, text_fields_total: 1, text_fields_confirmed: 1, errors: [], missing: [], rule_missing: [], warnings: [] }); window.__unexpectedApi.push(url.pathname); throw new Error('Unexpected browser API: ' + url.pathname); }; window.addEventListener('legacy-f2', () => window.__legacyF2++); }, { holdFlags }); await page.addScriptTag({ content: app }); await page.evaluate(() => { const state = window.__auxHeader.state; const timer = { id: 'main', action_id: 'hockey_game_timer', type: 'timer', title: 'Время периода', hidden: false, props: { runtimeHidden: true, mode: 'count_down', startTime: '20:00', endTime: '00:00', format: 'mm_ss', persist: false } }; state.config.components = [timer, { id: 'board', type: 'hockey_penalty_dashboard', action_id: 'hockey_penalty_dashboard', props: {} }]; state.config.shortcut_sequences = [{ id: 'legacyF2', name: 'Old saved F2', combo: 'F2', enabled: true, scope: 'runtime', steps: [{ id: 'dispatch', type: 'dispatch_event', enabled: true, condition: 'always', event_name: 'legacy-f2', detail: '{}' }] }]; state.data = { hockey: { selected_game: { external_id: 'g', date: '2026-11-03', home: { name: 'HOME', score: 0, players: [] }, away: { name: 'AWAY', score: 0, players: [] } }, language: { display: 'ru' }, operator_session: { token: 'session199' }, game_control: structuredClone(window.__control) } }; state.hockeyGameControl.g = structuredClone(window.__control); state.hockeyTimerGameId = 'g'; state.hockeySelectionContextGameId = 'g'; state.hockeySelectionGeneration = 1; window.UIBuilderRuntime.getTimer('hockey_game_timer'); Object.assign(state.timers.hockey_game_timer, { currentMs: 1043000, running: true, paused: false, startedOnce: true }); window.UIBuilderRuntime.openTimersEditor = () => window.__openedTimers++; window.__auxHeader.bindEvents(); }); await page.addScriptTag({ content: asset('hockey_data/static/match-status-header.js') }); const checkbox = page.locator('#hockeyMatchStatusHeader [data-hockey-auxiliary-toggle]'); await checkbox.waitFor({ state: 'visible' }); const settled = async () => page.waitForFunction(() => !window.UIBuilderRuntime.getAuxiliaryGate().pending); const flags = async () => page.evaluate(() => window.__flagRequests.map(row => row.flags.scoreboard_auxiliaries_enabled)); const clean = async () => { assert.deepEqual(errors, [], 'No browser page errors'); assert.deepEqual(unexpected, [], 'No external requests'); assert.deepEqual(await page.evaluate(() => window.__unexpectedApi), [], 'No swallowed unexpected API failures'); }; return { page, checkbox, settled, flags, clean, errors, unexpected }; } const cases = {}; cases.header_operator_f2 = async () => { const h = await fixture({ holdFlags: true }), { page, checkbox } = h; assert.equal(await checkbox.count(), 1, 'Hidden standalone timer must not duplicate the visible header checkbox'); assert.equal(await checkbox.isChecked(), false); assert.equal(await page.locator('.hockey-status-clock > span').textContent(), 'ОСНОВНОЙ ТАЙМЕР'); await checkbox.click(); await page.waitForFunction(() => window.__heldFlags.length === 1); assert.equal(await checkbox.isChecked(), true, 'Pending checkbox shows the requested ON state'); assert.deepEqual(await page.evaluate(() => { const gate = window.UIBuilderRuntime.getAuxiliaryGate(); return { pending: gate.pending, enabled: gate.enabled }; }), { pending: true, enabled: false }); await page.evaluate(() => { window.__holdFlags = false; window.__heldFlags.shift()(); }); await h.settled(); assert.equal(await checkbox.isChecked(), true); await page.locator('#focusBody').focus(); await page.keyboard.down('F2'); await page.keyboard.down('F2'); await h.settled(); await page.keyboard.up('F2'); assert.deepEqual(await h.flags(), [true, false], 'One physical F2 press performs exactly one flag write'); assert.equal(await checkbox.isChecked(), false); await page.keyboard.press('F2'); await h.settled(); assert.deepEqual(await h.flags(), [true, false, true]); assert.equal(await checkbox.isChecked(), true); assert.equal(await page.evaluate(() => window.__legacyF2), 0, 'Reserved F2 must not execute an old saved graphics shortcut'); await page.locator('#textEditor').focus(); await page.keyboard.press('F2'); await page.waitForTimeout(50); assert.deepEqual(await h.flags(), [true, false, true]); await page.locator('#focusBody').focus(); await page.evaluate(() => document.documentElement.classList.add('hockey-match-loading-active')); await page.keyboard.press('F2'); await page.evaluate(() => document.documentElement.classList.remove('hockey-match-loading-active')); await page.evaluate(() => { window.HockeyProgramLog = { isOpen: () => true }; }); await page.keyboard.press('F2'); await page.evaluate(() => { window.HockeyProgramLog = { isOpen: () => false }; }); assert.deepEqual(await h.flags(), [true, false, true]); const reference = await page.evaluate(() => window.__auxHeader.shortcutReferenceItems().filter(item => item.combo === 'F2')); assert.equal(reference.length, 1); assert.equal(reference[0].name, 'Плашки поверх счёта'); for (let n = 0; n < 3; n++) { await page.evaluate(() => { window.__auxHeader.state.timers.hockey_game_timer.currentMs -= 1000; window.dispatchEvent(new CustomEvent('hockey:auxiliary-gate-changed')); }); } await checkbox.click(); await h.settled(); assert.deepEqual(await h.flags(), [true, false, true, false], 'Header refreshes must not install duplicate checkbox listeners'); const geometry = await page.locator('.hockey-status-clock').evaluate(clock => { const outer = clock.getBoundingClientRect(), gate = clock.querySelector('.hockey-auxiliary-gate').getBoundingClientRect(); return { left: gate.left >= outer.left, right: gate.right <= outer.right, top: gate.top >= outer.top, bottom: gate.bottom <= outer.bottom }; }); assert.deepEqual(geometry, { left: true, right: true, top: true, bottom: true }, 'The control remains inside the main-timer card'); await h.clean(); }; cases.header_viewer_clock = async () => { const h = await fixture(), { page, checkbox } = h; await page.evaluate(() => { window.__checkboxNode = document.querySelector('[data-hockey-auxiliary-toggle]'); window.__clockNode = document.querySelector('.hockey-status-clock'); }); assert.equal(await page.locator('.hockey-status-clock strong').textContent(), '17:23'); await page.evaluate(() => { window.__auxHeader.state.timers.hockey_game_timer.currentMs = 1042000; }); await page.waitForFunction(() => document.querySelector('.hockey-status-clock strong').textContent === '17:22'); assert.equal(await page.evaluate(() => window.__checkboxNode === document.querySelector('[data-hockey-auxiliary-toggle]')), true, 'Clock ticks must retain the focused checkbox node'); assert.equal(await page.evaluate(() => window.__clockNode === document.querySelector('.hockey-status-clock')), true, 'Clock node remains stable for double-click editing'); await checkbox.dblclick(); await h.settled(); assert.equal(await page.evaluate(() => window.__openedTimers), 0, 'Checkbox double-click must not open the clock editor'); await page.locator('.hockey-status-clock strong').dblclick(); assert.equal(await page.evaluate(() => window.__openedTimers), 1); const before = await h.flags(); await page.evaluate(() => { window.__rights = false; window.__accessGeneration++; window.dispatchEvent(new CustomEvent('hockey:access-changed')); }); await page.waitForFunction(() => document.querySelector('[data-hockey-auxiliary-toggle]').disabled); assert.equal(await checkbox.isDisabled(), true); await page.locator('#focusBody').focus(); await page.keyboard.press('F2'); await page.waitForTimeout(50); assert.deepEqual(await h.flags(), before); assert.equal(await page.evaluate(() => window.__legacyF2), 0); await h.clean(); }; async function main() { const name = process.argv[2]; assert.ok(cases[name], name); try { await cases[name](); console.log(`PASS BUILD199 auxiliary header: ${name}`); } finally { await browser?.close(); } } main().catch(error => { console.error(error); process.exitCode = 1; });