Files
pulse/frontend-modern/browser-tests/reverse-proxy-help.cjs
T
pulse-triage[bot] c381995d24 Keep proxy-auth help navigation independent of emoji fragments
The real Docs browser check reached the guide but could not resolve the guessed emoji heading fragment. Link the existing guide directly and verify its trust-boundary heading and text without inventing an anchor.

Contract-Neutral: Documentation and browser regression checks only; rendering, proxy trust and authentication runtime contracts are unchanged.
Change-source: pulse-maintainer
2026-10-03 19:47:00 +01:00

123 lines
6.2 KiB
JavaScript

// Real shipped Docs/router/CSS, with no backend, proxy or real credential.
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { createHash } = require('node:crypto');
const { chromium, webkit } = require('playwright');
(async () => {
const workspace = process.cwd();
const root = path.join(workspace, 'frontend-modern');
const output = path.join(workspace, 'tmp', 'reverse-proxy-help');
fs.mkdirSync(output, { recursive: true });
const binding = JSON.parse(fs.readFileSync(path.join(output, 'binding.json'), 'utf8'));
assert.match(binding.sourceSha, /^[0-9a-f]{40}$/);
for (const [file, digest] of Object.entries(binding.contentSha256)) {
assert.equal(
createHash('sha256').update(fs.readFileSync(path.join(workspace, file))).digest('hex'),
digest,
file,
);
}
process.chdir(root);
const { createServer } = await import(path.join(root, 'node_modules/vite/dist/node/index.js'));
const server = await createServer({
root,
configFile: path.join(root, 'vite.config.ts'),
cacheDir: path.join(root, 'node_modules', '.vite-reverse-proxy-help'),
server: { host: '127.0.0.1', port: 5268, strictPort: true },
optimizeDeps: { noDiscovery: true, include: [] },
});
const observations = [];
let browser;
const inViewport = (id) => {
const heading = document.getElementById(id);
return heading && heading.getBoundingClientRect().top >= 0 &&
heading.getBoundingClientRect().top < innerHeight;
};
try {
await server.listen();
for (const [engine, width, height, tone] of [
['chromium', 1280, 1000, 'light'], ['webkit', 390, 844, 'dark'],
]) {
browser = await { chromium, webkit }[engine].launch(engine === 'chromium'
? { headless: true, channel: 'chromium', args: ['--no-sandbox'] }
: { headless: true });
const page = await browser.newPage({
viewport: { width, height },
...(engine === 'webkit' ? { isMobile: true, hasTouch: true } : {}),
});
const errors = [];
page.on('pageerror', (error) => errors.push(error.message));
page.on('console', (message) => {
if (message.type() === 'error') errors.push(message.text());
});
await page.goto('http://127.0.0.1:5268/browser-tests/docs-fragment-navigation.html');
if (tone === 'dark') await page.evaluate(() => document.documentElement.classList.add('dark'));
await page.getByRole('link', { name: '← All documentation' }).click();
const entry = page.locator('a[data-doc-link][href="/docs/REVERSE_PROXY"]').first();
await entry.focus();
await page.keyboard.press('Enter');
const heading = page.locator('#before-configuring-the-proxy');
await heading.waitFor();
await heading.scrollIntoViewIfNeeded();
let text = await page.locator('article').innerText();
for (const phrase of [
'configured immediate peer', 'PULSE_TRUSTED_PROXY_CIDRS=127.0.0.1/32',
'Wildcard ranges', 'rejected at startup', 'not an authentication bypass',
'Adding the header alone is not sufficient',
]) assert.ok(text.includes(phrase), phrase);
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
await page.screenshot({ path: path.join(output, `${engine}-trust.png`) });
const nginx = page.locator('pre').filter({ hasText: 'proxy_set_header X-Forwarded-For $remote_addr;' });
await nginx.scrollIntoViewIfNeeded();
const copied = await nginx.innerText();
assert.ok(copied.includes('proxy_set_header X-Forwarded-Proto $scheme;'));
assert.ok(copied.includes('proxy_set_header Forwarded "";'));
assert.ok(!copied.includes('$proxy_add_x_forwarded_for'));
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1));
await page.screenshot({ path: path.join(output, `${engine}-nginx.png`) });
const configuration = page.getByRole('link', { name: 'configuration overrides', exact: true });
await configuration.focus();
await page.keyboard.press('Enter');
await page.waitForFunction(inViewport, 'common-overrides-environment-variables');
text = await page.locator('article').innerText();
assert.ok(text.includes('forwarded client IP, scheme, host and port'));
const back = page.getByRole('link', { name: 'Reverse Proxy', exact: true });
await back.focus();
await page.keyboard.press('Enter');
await page.waitForFunction(inViewport, 'before-configuring-the-proxy');
assert.ok(page.url().endsWith('/docs/REVERSE_PROXY#before-configuring-the-proxy'));
const authentication = page.getByRole('link', { name: 'proxy authentication guide', exact: true });
await authentication.focus();
await page.keyboard.press('Enter');
const boundary = page.getByRole('heading', { name: '⚠️ Header Trust Boundary', exact: true });
await boundary.waitFor();
assert.ok(page.url().endsWith('/docs/PROXY_AUTH'));
await boundary.scrollIntoViewIfNeeded();
assert.ok((await page.locator('article').innerText()).includes('never append to them'));
assert.deepEqual(errors, []);
observations.push({
engine, width, height, tone, browserVersion: browser.version(),
trustWarningRendered: true, safeNginxBlockRendered: true,
keyboardConfigurationRoundTrip: true, keyboardAuthenticationBoundary: true,
noPageOverflow: true, errors,
});
await browser.close();
browser = undefined;
}
const result = {
result: 'passed', playwrightVersion: require('playwright/package.json').version,
sourceSha: binding.sourceSha, verifiedAt: new Date().toISOString(),
contentSha256: binding.contentSha256, dependencySnapshot: binding.dependencySnapshot,
scope: 'Production Docs rendering/navigation; not native proxy configuration, TLS or SSO acceptance',
observations,
};
fs.writeFileSync(path.join(output, 'result.json'), JSON.stringify(result, null, 2) + '\n');
console.log(JSON.stringify(result));
} finally {
if (browser) await browser.close();
await server.close();
}
})().catch((error) => { console.error(error); process.exitCode = 1; });