// Ticket 14 browser acceptance. real transport uses controlled HTTP responses, // not a deployed-backend integration claim (that belongs to ticket 18). import assert from 'node:assert/strict' import { registerCockpitMocks } from '../../../src/modules/cockpit/mocks/contractMocks.js' import { registerMockTransport, findMockTransport } from '../../../src/modules/cockpit/services/cockpitApi.js' const base = process.env.COCKPIT_BASE || 'http://localhost:8001' const puppeteer = (await import(process.env.PUPPETEER_CORE_PATH || 'file:///C:/Users/ASUS/.dsh/puppeteer-runner/node_modules/puppeteer-core/lib/puppeteer/puppeteer-core.js')).default registerCockpitMocks({ handlers: { registerMockTransport } }) const browser = await puppeteer.launch({ executablePath: process.env.CHROME_PATH || 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', headless: true, defaultViewport: { width: 1680, height: 950 }, args: ['--no-sandbox'] }) let passed = 0 function check(name, condition, evidence) { assert.ok(condition, `${name}: ${JSON.stringify(evidence)}`); passed++; console.log(`PASS ${name}: ${JSON.stringify(evidence)}`) } async function open(config, controlled = false) { const page = await browser.newPage() const paths = [] if (controlled) { await page.setRequestInterception(true) page.on('request', async request => { const url = new URL(request.url()) if (!url.pathname.startsWith('/ticket14-api/')) return request.continue() const path = url.pathname.slice('/ticket14-api'.length) + url.search paths.push(path) if (url.pathname.endsWith('/monitoring/overview')) return request.respond({ status: 401, body: '{}' }) const handler = findMockTransport(path) if (!handler) return request.respond({ status: 404, body: '{}' }) try { const data = await handler({ path }); await request.respond({ status: 200, contentType: 'application/json', body: JSON.stringify(data) }) } catch { await request.respond({ status: 500, body: '{}' }) } }) } await page.evaluateOnNewDocument(config => { window.__COCKPIT_CONFIG__ = config }, config) await page.goto(`${base}/#/cockpit`, { waitUntil: 'domcontentloaded', timeout: 60000 }) await page.waitForSelector('.cockpit-shell') return { page, paths } } const map = { intro: { enabled: false }, cesium: { enabled: false } } try { { const { page } = await open({ dataSource: 'mock', map }) await page.$eval('.cockpit-shell__mode:nth-child(3)', r => r.click()) await page.waitForSelector('[data-pie-slice]') const counts = () => page.$$eval('[data-legend-key]', rows => rows.map(r => [r.dataset.legendKey, r.dataset.statusCount])) const before = await counts() await page.$eval('[data-legend-key="ALARM"]', r => r.click()) await page.$eval('[data-legend-key="WARNING"]', r => r.click()) await page.waitForFunction(() => document.querySelector('[data-legend-key="ALARM"]')?.dataset.statusSelected === 'yes' && document.querySelector('[data-legend-key="WARNING"]')?.dataset.statusSelected === 'yes') const selected = await page.$$eval('[data-legend-key][data-status-selected="yes"]', rows => rows.map(r => r.dataset.legendKey).sort()) check('two-status OR selection', selected.join(',') === 'ALARM,WARNING', selected) check('global counts stable', JSON.stringify(await counts()) === JSON.stringify(before), before) await page.waitForSelector('[data-pie-slice][data-pie-selected="yes"]').catch(async error => { console.log(await page.$eval('.cockpit-shell', r => r.innerText)); throw error }) const pie = await page.$$eval('[data-pie-slice]', rows => rows.map(r => ({ key: r.dataset.pieSlice, selected: r.dataset.pieSelected, dim: r.classList.contains('is-dim'), path: r.getAttribute('d') }))) check('pie selection and dimming', pie.filter(r => r.selected === 'yes').length === 2 && pie.filter(r => r.selected !== 'yes').every(r => r.dim), pie) check('pie has finite geometry', pie.every(r => !/NaN|Infinity/.test(r.path)), pie) await page.$eval('[data-legend-key="ALARM"]', r => r.click()) check('remove one status preserves other', await page.$eval('[data-legend-key="WARNING"]', r => r.dataset.statusSelected === 'yes'), null) await page.close() } { const { page, paths } = await open({ dataSource: 'real', api: { baseUrl: `${base}/ticket14-api` }, map, runtime: { refreshSeconds: 3600 } }, true) await page.$eval('.cockpit-shell__mode:nth-child(3)', r => r.click()) await page.waitForSelector('[data-section-name="monitoringOverview"][data-section-state="unavailable"]') await page.waitForSelector('[data-section-name="monitoringDistribution"][data-section-state="success"]') check('real transport auto starts and isolates 401', paths.some(p => p.startsWith('/map-snapshot')) && paths.some(p => p.startsWith('/monitoring/situation')) && paths.some(p => p.startsWith('/overview/')) && paths.some(p => p.startsWith('/infrastructure/')), paths) await page.close() } { const { page } = await open({ dataSource: 'real', api: { baseUrl: '' }, map }) await page.waitForSelector('[data-section-name="equipmentAggregate"][data-section-state="unavailable"]') await page.waitForSelector('[data-section-name="mapSnapshot"][data-section-state="unavailable"]') check('NO_BASE_URL unavailable without mock fallback', await page.$eval('[data-section-name="mapSnapshot"]', r => r.textContent.includes('不可用')), null) await page.close() } } finally { await browser.close() } console.log(`T14 final walkthrough: ${passed} passed`)