Files
pulse/frontend-modern/browser-tests/doc-command-help.cjs
T
pulse-triage[bot] 1205f276e2 Prepare agent-command Docs proof outside the browser compiler tree
Keep the original output-bound attempt stopped. Compile the existing Docs fetch, renderer, router and shipped help in the offline source VM, then exercise exact static bytes at desktop and phone with a small bounded browser process and native captures. No runtime or tool-boundary change.

Contract-Neutral: Test-only consuming Docs fixture and source-bound preview preparation; production code, routes, installer/runtime contracts and enforced browser limits are unchanged.
Change-source: pulse-maintainer
2026-10-13 13:26:14 +01:00

137 lines
8.3 KiB
JavaScript

const fs = require('node:fs');
const path = require('node:path');
const http = require('node:http');
const crypto = require('node:crypto');
const assert = require('node:assert/strict');
const { chromium } = require('playwright');
const root = '/workspace';
const frontend = path.join(root, 'frontend-modern');
const evidence = path.join(root, 'tmp/doc-command-help');
const preview = path.join(evidence, 'static');
const hash = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
const bindingBytes = fs.readFileSync(path.join(preview, 'binding.json'));
const binding = JSON.parse(bindingBytes);
const result = { source: binding.source_sha, binding_sha256: hash(bindingBytes),
cases: [], captures: [], pageErrors: [], requests: [], served: [] };
const failure = 'copied-agent-command-fails-before-installation';
(async () => {
let server, browser;
try {
for (const [file, expected] of Object.entries(binding.sources))
assert.equal(hash(fs.readFileSync(path.join(frontend, file))), expected, file);
for (const [file, expected] of Object.entries(binding.artifacts))
assert.equal(hash(fs.readFileSync(path.join(preview, file))), expected, file);
result.source_inputs = Object.keys(binding.sources).length;
server = http.createServer((req, res) => {
const route = new URL(req.url, 'http://fixture.invalid').pathname;
let asset = route.slice(1);
if (route.startsWith('/docs/') && !route.endsWith('.md'))
asset = 'browser-tests/doc-command-help.html';
if (!Object.hasOwn(binding.artifacts, asset)) { res.writeHead(404); res.end(); return; }
const bytes = fs.readFileSync(path.join(preview, asset));
assert.equal(hash(bytes), binding.artifacts[asset]);
result.served.push({route, asset, sha256: hash(bytes)});
const type = asset.endsWith('.md') ? 'text/markdown' : asset.endsWith('.css') ? 'text/css' :
asset.endsWith('.js') ? 'text/javascript' : 'text/html';
res.writeHead(200, {'Content-Type': type, 'Cache-Control': 'no-store'}); res.end(bytes);
});
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
const origin = `http://127.0.0.1:${server.address().port}`;
browser = await chromium.launch({headless:true, channel:'chromium', args:['--no-sandbox']});
result.browser = browser.version(); result.playwright = require('playwright/package.json').version;
assert.equal(result.playwright, '1.56.1');
for (const state of [
{ name: 'desktop-light', width: 1360, height: 960, dark: false },
{ name: 'phone-dark', width: 390, height: 844, dark: true },
]) {
const context = await browser.newContext({ viewport: { width: state.width, height: state.height },
isMobile: state.width < 500, hasTouch: state.width < 500 });
const page = await context.newPage();
page.on('pageerror', error => result.pageErrors.push(error.message));
page.on('request', req => result.requests.push(new URL(req.url()).pathname));
await page.addInitScript(dark => {
document.addEventListener('DOMContentLoaded', () => document.documentElement.classList.toggle('dark', dark));
}, state.dark);
const capture = async (name) => {
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth), true, name);
const file = `${state.name}-${name}.png`;
await page.screenshot({ path: path.join(evidence, file) });
result.captures.push({ file, sha256: hash(fs.readFileSync(path.join(evidence, file))), state });
};
const jump = async (selector) => {
await page.locator(selector).evaluate(el => window.scrollBy(0, el.getBoundingClientRect().top - 64));
};
const activate = async (locator) => {
if (state.width < 500) await locator.tap();
else { await locator.focus(); await page.keyboard.press('Enter'); }
};
await page.goto(`${origin}/docs/FAQ#how-do-i-add-a-node`);
await page.locator('article #how-do-i-add-a-node').waitFor();
await jump('#how-do-i-add-a-node');
await capture('faq');
assert.match(await page.locator('article').innerText(), /token only at its private prompt/);
const failureLink = page.locator(`article a[href="/docs/UNIFIED_AGENT#${failure}"]`);
await activate(failureLink);
await page.waitForURL(`**/docs/UNIFIED_AGENT#${failure}`);
await page.locator(`#${failure}`).waitFor();
await jump(`#${failure}`);
await capture('failure-stage');
assert.match(await page.locator('article').innerText(), /A real terminal can still produce this error/);
const stop = page.locator('article p').filter({ hasText: 'Stop rather than bypass the private prompt.' });
assert.equal(await stop.count(), 1);
await stop.evaluate(el => window.scrollBy(0, el.getBoundingClientRect().top - 64));
await capture('failure-safety');
await page.evaluate(() => window.__pi_docs_shell_marker = 'same-shell');
const prep = page.locator(`#${failure} ~ p a[href="#private-file-installation-linux-macos-and-nas"]`).first();
await activate(prep);
await page.waitForURL('**#private-file-installation-linux-macos-and-nas');
assert.equal(await page.evaluate(() => window.__pi_docs_shell_marker), 'same-shell');
assert.equal(await page.locator('#private-file-installation-linux-macos-and-nas').evaluate(el => {
const box = el.getBoundingClientRect(); return box.top >= 0 && box.top < window.innerHeight;
}), true);
await page.goBack();
await page.waitForURL(`**/docs/UNIFIED_AGENT#${failure}`);
await page.goto(`${origin}/docs/TROUBLESHOOTING#choose-the-affected-workflow`);
await page.locator('article #choose-the-affected-workflow').waitFor();
await page.locator('article li').filter({ hasText: 'Copied agent install or removal command failed:' })
.evaluate(el => window.scrollBy(0, el.getBoundingClientRect().top - 64));
await capture('troubleshooting');
await activate(page.locator(`article a[href="/docs/UNIFIED_AGENT#${failure}"]`));
await page.waitForURL(`**/docs/UNIFIED_AGENT#${failure}`);
await page.locator(`#${failure}`).waitFor();
for (const id of ['agent-lookup-returns-http-401-or-403', 'auto-update', 'uninstall']) {
const link = page.locator(`#${failure} ~ p a[href="#${id}"]`).first();
await activate(link); await page.waitForURL(`**#${id}`);
await page.locator(`#${id}`).waitFor();
await page.goBack(); await page.waitForURL(`**#${failure}`);
}
for (const name of ['FAQ', 'UNIFIED_AGENT', 'TROUBLESHOOTING']) {
const response = await page.request.get(`${origin}/docs/${name}.md`);
assert.equal(response.status(), 200);
assert.equal(hash(await response.body()), binding.sources[`public/docs/${name}.md`]);
}
result.cases.push({ ...state, interactions: ['FAQ to failure', 'private-file anchor and history back',
'Troubleshooting to failure', 'reporting/update/removal anchors and history back'],
method: state.width < 500 ? 'touch tap' : 'keyboard Enter',
horizontalOverflow: false, documentBytesMatched: true, sameShell: true });
await context.close();
}
assert.deepEqual(result.pageErrors, []);
assert.equal(result.requests.some(url => url.startsWith('/api/') || url.startsWith('/ws')), false);
result.result = 'passed';
} catch (error) {
result.result = 'failed'; result.error = String(error.stack || error);
process.exitCode = 1;
} finally {
if (browser) { await browser.close(); result.browser_closed = true; }
if (server) { await new Promise(resolve => server.close(resolve)); result.server_closed = true; }
fs.writeFileSync(path.join(evidence, 'browser-result.json'), JSON.stringify(result, null, 2) + '\n');
// Do not send input manifests, logs or screenshot bytes through the capture.
console.log(JSON.stringify({source: result.source, result: result.result,
cases: result.cases.length, captures: result.captures.length,
source_inputs: result.source_inputs, browser_closed: result.browser_closed,
server_closed: result.server_closed, error: result.error}));
}
})().catch(error => { console.error(error); process.exitCode = 1; });